271 lines
14 KiB
Markdown
271 lines
14 KiB
Markdown
# LDAP Portal
|
|
|
|
Self-Service-Portal fuer LLDAP mit NestJS API, Angular Frontend und eigenem OpenID-Connect-Provider fuer Web-SSO.
|
|
|
|
Die Anwendung ist als Identity-Portal vor einem bestehenden LLDAP gedacht: Nutzer koennen sich registrieren, E-Mail und Passwort verwalten, Administratoren koennen Registrierungen, Nutzer, Gruppen und OIDC-Clients pflegen.
|
|
|
|
|
|
## Funktionen
|
|
|
|
- Registrierung mit E-Mail-Verifikation und anschliessender Admin-Freigabe
|
|
- Login gegen LDAP/LLDAP
|
|
- Passwortaenderung nach erfolgreichem LDAP-Login
|
|
- Passwort-Reset ueber eigene Tokens und SMTP
|
|
- Profilbearbeitung, E-Mail-Aenderung mit Verifikation und Account-Loeschanfrage
|
|
- Admin-Bereiche fuer Registrierungen, Nutzer, Gruppen und Audit
|
|
- Audit-Events fuer sicherheitsrelevante Aktionen in MySQL
|
|
- OpenID Connect Provider fuer Web-SSO
|
|
- Single-Container-Deployment mit Nginx, Angular und NestJS API
|
|
|
|
## Architektur
|
|
|
|
Das Portal besteht aus drei Teilen:
|
|
|
|
- Angular Web-UI: Login, Registrierung, Konto- und Admin-Oberflaechen.
|
|
- NestJS API: REST-Endpunkte, OIDC Provider, Mailversand, Audit, LLDAP-Integration.
|
|
- Nginx im Container: liefert die Web-UI aus und routet API/OIDC-Pfade intern zur NestJS API.
|
|
|
|
Externe Dienste werden nicht mitgeliefert und muessen vorhanden sein:
|
|
|
|
- MySQL-Datenbank fuer Portal-Daten, Registrierungen, Tokens, Audit und OIDC-Speicher.
|
|
- LLDAP HTTP/GraphQL-Endpunkt fuer administrative Operationen.
|
|
- LLDAP LDAP-Endpunkt fuer Passwortpruefung und Passwortaenderung.
|
|
- SMTP-Server fuer Verifikations- und Passwort-Reset-Mails.
|
|
|
|
## Lokale Entwicklung
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
npm install
|
|
npm run start:api
|
|
npm run start:web
|
|
```
|
|
|
|
In der lokalen Entwicklung laeuft die API standardmaessig auf `http://localhost:3000`, das Frontend auf `http://localhost:4200`.
|
|
|
|
Wichtige lokale URLs:
|
|
|
|
- Web UI: `http://localhost:4200`
|
|
- API: `http://localhost:3000`
|
|
- OIDC Discovery bei direkter API-Nutzung: `http://localhost:3000/.well-known/openid-configuration`
|
|
|
|
## Deployment
|
|
|
|
### Voraussetzungen
|
|
|
|
Vor dem Start muessen erreichbar sein:
|
|
|
|
- MySQL, z. B. `mysql://ldap_portal:secret@mysql.example.com:3306/ldap_portal`
|
|
- LLDAP Web/GraphQL, z. B. `https://lldap.example.com`
|
|
- LLDAP LDAP, z. B. `ldap://lldap.example.com:3890`
|
|
- SMTP, z. B. `smtp.example.com:587`
|
|
|
|
Erzeuge produktive Secrets fuer mindestens:
|
|
|
|
- `JWT_SECRET`
|
|
- `TOKEN_SECRET`
|
|
- `OIDC_COOKIE_SECRET`
|
|
- `LLDAP_ADMIN_PASSWORD`
|
|
- `SMTP_PASS`
|
|
|
|
`JWT_SECRET`, `TOKEN_SECRET` und `OIDC_COOKIE_SECRET` muessen lang, zufaellig und stabil sein. Wenn sie nach dem Deployment geaendert werden, werden bestehende Sessions, Tokens oder verschluesselte Portal-Daten unbrauchbar.
|
|
|
|
### Single Container
|
|
|
|
Das Root-`Dockerfile` baut API und Angular in ein einzelnes Image. Der Container startet:
|
|
|
|
- Nginx Web-UI und Reverse Proxy auf Port `8080`
|
|
- NestJS API / IdP intern auf Port `3000`
|
|
|
|
Build und Push:
|
|
|
|
```bash
|
|
docker build -t registry.example.com/ldap-portal/idp:latest .
|
|
docker push registry.example.com/ldap-portal/idp:latest
|
|
```
|
|
|
|
Start:
|
|
|
|
```bash
|
|
docker run -d --name ldap-portal-idp \
|
|
--env-file .env \
|
|
-p 8080:8080 \
|
|
registry.example.com/ldap-portal/idp:latest
|
|
```
|
|
|
|
Docker Compose:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
docker compose up --build
|
|
```
|
|
|
|
### Reverse Proxy und externe URLs
|
|
|
|
Der Container erwartet eingehenden Traffic auf Port `8080`. Ein externer Reverse Proxy kann davor TLS terminieren.
|
|
|
|
Bei produktivem Betrieb hinter `https://idp.example.com` sollten diese Werte zusammenpassen:
|
|
|
|
```env
|
|
NODE_ENV=production
|
|
PUBLIC_WEB_URL=https://idp.example.com
|
|
OIDC_ISSUER=https://idp.example.com
|
|
OIDC_TRUST_PROXY=true
|
|
API_BASE_URL=/api
|
|
```
|
|
|
|
`PUBLIC_WEB_URL` wird fuer CORS, E-Mail-Links und den Registrierungslink auf der SSO-Loginseite verwendet.
|
|
|
|
`OIDC_ISSUER` muss exakt die externe Issuer-URL sein, die SSO-Clients verwenden. Wenn der Dienst hinter TLS laeuft, muss hier die `https://`-URL stehen.
|
|
|
|
`API_BASE_URL` sollte im Single-Container-Deployment bei `/api` bleiben. Das Frontend ruft dann `/api/...` auf; Nginx entfernt den Prefix intern und routet zur NestJS API. Die OIDC-Pfade `/.well-known/`, `/oidc/` und `/interaction/` werden ebenfalls zur API geroutet.
|
|
|
|
## Konfiguration
|
|
|
|
Die wichtigsten Variablen aus `.env.example`:
|
|
|
|
| Variable | Bedeutung |
|
|
| --- | --- |
|
|
| `NODE_ENV` | In Produktion `production`; deaktiviert TypeORM `synchronize` und aktiviert sichere OIDC-Cookies. |
|
|
| `API_PORT` | Interner Port der NestJS API, im Container standardmaessig `3000`. |
|
|
| `PUBLIC_WEB_URL` | Externe Web-URL fuer CORS, Mail-Links und Registrierung aus dem SSO-Login. |
|
|
| `API_BASE_URL` | API-Basis aus Sicht des Browsers, im Single Container `/api`. |
|
|
| `APP_PRODUCT_NAME`, `APP_COMPANY_NAME` | Zentrale Branding-Namen fuer OIDC-Seiten und Mail-Templates. |
|
|
| `APP_PRIMARY_COLOR` | Zentrale Primaerfarbe als sechsstelliger Hex-Wert. |
|
|
| `APP_SUPPORT_EMAIL`, `APP_LOGO_URL`, `APP_IMPRINT_URL`, `APP_PRIVACY_URL` | Zentrale Branding-Werte; Bild- und Link-URLs muessen HTTPS verwenden. |
|
|
| `DATABASE_URL` | MySQL-Verbindungs-URL. Alternativ `DB_HOST`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD`, `DB_DATABASE`. |
|
|
| `DATABASE_SSL` | `true`, wenn MySQL TLS verlangt. |
|
|
| `JWT_SECRET` | Signatur-Secret fuer Portal-JWTs. |
|
|
| `TOKEN_SECRET` | Verschluesselung/Hashing fuer Portal-Tokens und gespeicherte Secrets. |
|
|
| `LLDAP_URL` | LLDAP Web/GraphQL-Basis-URL ohne `/api/graphql`. |
|
|
| `LLDAP_LDAP_URL` | LDAP-Endpunkt fuer Bind und Password Modify. |
|
|
| `LLDAP_BASE_DN` | LDAP Base DN, z. B. `dc=example,dc=com`. |
|
|
| `LLDAP_ADMIN_USERNAME` | LLDAP Admin-User fuer GraphQL und LDAP-Admin-Bind. |
|
|
| `LLDAP_ADMIN_PASSWORD` | Passwort des LLDAP Admin-Users. |
|
|
| `LLDAP_GRAPHQL_TOKEN` | Optionaler statischer Bearer Token fuer GraphQL statt Login per Admin-User. |
|
|
| `LLDAP_DEFAULT_GROUP` | Optionale numerische LLDAP-Gruppen-ID, in die freigegebene neue Nutzer aufgenommen werden. |
|
|
| `SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE` | SMTP-Verbindung. |
|
|
| `SMTP_USER`, `SMTP_PASS` | Optionale SMTP-Authentifizierung. |
|
|
| `SMTP_FROM` | Absenderadresse fuer Portal-Mails. |
|
|
| `MAIL_PRODUCT_NAME`, `MAIL_COMPANY_NAME` | Zentrale Branding-Namen fuer Mail-Templates. |
|
|
| `MAIL_PRIMARY_COLOR` | Primaerfarbe fuer Mail-Buttons und Links. |
|
|
| `MAIL_SUPPORT_EMAIL`, `MAIL_LOGO_URL`, `MAIL_IMPRINT_URL`, `MAIL_PRIVACY_URL` | Optionale Branding- und Footer-Werte fuer Mails. |
|
|
| `OIDC_ISSUER` | Externe Issuer-URL des OIDC Providers. |
|
|
| `OIDC_COOKIE_SECRET` | Cookie-Secret fuer OIDC Sessions; Fallback ist `TOKEN_SECRET`. |
|
|
| `OIDC_ADMIN_GROUP` | Gruppe fuer OIDC-Clientverwaltung, Standard `client_manager`. |
|
|
| `OIDC_ADMIN_GROUP_UUID` | Zusaetzlich akzeptierte Gruppen-UUID fuer OIDC-Clientverwaltung. |
|
|
| `OIDC_TRUST_PROXY` | `true`, wenn ein Reverse Proxy TLS/Host-Header setzt. |
|
|
| `USER_MANAGER_GROUP` | LLDAP-Gruppe, deren Mitglieder per E-Mail benachrichtigt werden, wenn eine Registrierung nach E-Mail-Bestaetigung auf Freigabe wartet. Standard `user_manager`. |
|
|
|
|
Hinweis: `REGISTRATION_MANAGER_GROUP`, `GROUP_MANAGER_GROUP` und `AUDIT_VIEWER_GROUP` stehen aktuell in `.env.example`, werden im Code aber nicht ausgewertet. Die Admin-Gruppennamen sind derzeit fest verdrahtet, siehe "Admin-Rollen".
|
|
|
|
Die `APP_*`-Werte haben Vorrang. Solange sie nicht gesetzt sind, verwendet die Anwendung fuer die Rueckwaertskompatibilitaet die entsprechenden `MAIL_*`-Werte. Die OIDC-Seiten liegen unter `apps/api/src/oidc/templates` und werden beim API-Build nach `dist/oidc/templates` kopiert. Sie werden ohne clientseitiges JavaScript direkt durch NestJS gerendert.
|
|
|
|
## Datenbank
|
|
|
|
Die App nutzt TypeORM mit MySQL. In `NODE_ENV=production` ist `synchronize` deaktiviert. Fuer produktive Deployments muss das Schema vorab vorhanden sein oder es muessen Migrationen ergaenzt und ausgefuehrt werden.
|
|
|
|
In Entwicklung kann TypeORM das Schema automatisch synchronisieren, solange `NODE_ENV` nicht `production` ist.
|
|
|
|
## Registrierung und Nutzerfluss
|
|
|
|
Die Registrierung laeuft in zwei Schritten:
|
|
|
|
1. Nutzer registrieren sich mit E-Mail, Anzeigename und Passwort.
|
|
2. Der Nutzer bestaetigt seine E-Mail ueber den Mail-Link.
|
|
3. Ein Admin mit Gruppe `registration_manager` gibt die Registrierung frei.
|
|
4. Erst bei Freigabe wird der LLDAP-User erstellt und optional in `LLDAP_DEFAULT_GROUP` aufgenommen.
|
|
|
|
Nach Schritt 2 sendet das Portal eine Benachrichtigung an die E-Mail-Adressen der Mitglieder aus `USER_MANAGER_GROUP`, damit offene Freigaben sichtbar werden. Wenn die Gruppe nicht gefunden wird, keine Mitglieder mit E-Mail-Adresse hat oder der Mailversand fehlschlaegt, bleibt die Registrierung trotzdem im Status `pending_approval`; der Fehler wird im Audit protokolliert.
|
|
|
|
Passwortregeln:
|
|
|
|
- mindestens 12 Zeichen
|
|
- mindestens ein Kleinbuchstabe
|
|
- mindestens ein Grossbuchstabe
|
|
- mindestens eine Ziffer
|
|
|
|
Der Benutzername in LLDAP ist die E-Mail-Adresse der Registrierung.
|
|
|
|
## Admin-Rollen
|
|
|
|
Admin-Berechtigungen werden ueber LLDAP-Gruppen gesteuert:
|
|
|
|
- `client_manager`: OIDC-Clients verwalten.
|
|
- `registration_manager`: Registrierungen freigeben oder ablehnen.
|
|
- `user_manager`: Nutzer anzeigen, bearbeiten, loeschen und Gruppenmitgliedschaften aendern.
|
|
- `group_manager`: Gruppen anzeigen, erstellen, bearbeiten und loeschen.
|
|
- `audit_viewer`: Audit-Events anzeigen.
|
|
|
|
Die allgemeinen Admin-Rollen werden aktuell per Gruppen-Displayname geprueft. Die OIDC-Clientverwaltung akzeptiert zusaetzlich die konfigurierte `OIDC_ADMIN_GROUP_UUID`.
|
|
|
|
Wichtig fuer die Erstinbetriebnahme: Der erste Admin muss in LLDAP bereits existieren und in die benoetigten Gruppen aufgenommen werden, bevor er die Admin-Bereiche im Portal nutzen kann.
|
|
|
|
## OpenID Connect
|
|
|
|
Die API stellt einen OIDC Provider bereit. Die wichtigsten Endpunkte:
|
|
|
|
- Discovery: `/.well-known/openid-configuration`
|
|
- Authorization: `/oidc/auth`
|
|
- Token: `/oidc/token`
|
|
- UserInfo: `/oidc/me`
|
|
- JWKS: `/oidc/jwks`
|
|
- Logout: `/oidc/session/end`
|
|
- Revocation: `/oidc/token/revocation`
|
|
- Introspection: `/oidc/token/introspection`
|
|
- Interactions/Login/Consent: `/interaction/...`
|
|
|
|
OIDC-Clients werden im Frontend unter `/admin/oidc-clients` verwaltet. Zugriff erhaelt ein eingeloggter Nutzer mit OIDC-Admin-Berechtigung. Client Secrets werden nur direkt nach Erstellung oder Secret-Rotation angezeigt.
|
|
|
|
V1 unterstuetzt Authorization Code Flow mit verpflichtendem PKCE. Dynamic Client Registration und SAML sind nicht aktiviert.
|
|
|
|
### Client-Optionen
|
|
|
|
- `Redirect URIs`: erlaubte Callback-URLs des SSO-Clients.
|
|
- `Logout Redirect URIs`: erlaubte Redirects nach RP-initiated Logout.
|
|
- `Scopes`: Scopes, die der Client verwenden darf, z. B. `openid profile email groups`.
|
|
- `Public Client ohne Secret`: erstellt einen Client ohne Secret und mit `token_endpoint_auth_method=none`; gedacht fuer Browser/native Apps, die kein Secret sicher halten koennen.
|
|
- Confidential Client: Standard, erzeugt ein Client Secret und verwendet `client_secret_basic`.
|
|
- `Gruppen-Claim ausgeben`: steuert, ob der `groups` Claim in OIDC-Claims ausgegeben wird.
|
|
- `First-Party Client`: fuer eigene/interne Clients. Nach erfolgreichem Login wird die Consent-Seite uebersprungen und die angeforderten Berechtigungen werden automatisch bestaetigt.
|
|
|
|
Nach der Erstellung koennen Name, Redirect URIs, Logout Redirect URIs, Scopes, First-Party-Flag, Gruppen-Claim und Aktivstatus im Admin-UI bearbeitet werden. Die `client_id` bleibt stabil.
|
|
|
|
Confidential Clients koennen ihr Client Secret im Admin-UI rotieren. Das neue Secret wird nur einmal angezeigt und muss danach in der angebundenen Anwendung hinterlegt werden. Bestehende Grants, Sessions und Refresh Tokens werden durch die Rotation nicht aktiv widerrufen; neue Token-Requests muessen aber das neue Secret verwenden. Public Clients haben kein Secret und koennen daher nicht rotiert werden.
|
|
|
|
## LLDAP-Hinweise
|
|
|
|
Die API nutzt:
|
|
|
|
- LDAP-Bind fuer Login und Passwortpruefung.
|
|
- LDAP Password Modify Extended Operation fuer Passwortaenderungen.
|
|
- GraphQL fuer User-, Gruppen- und Admin-Operationen.
|
|
|
|
Der LLDAP Admin-User muss ausreichende Rechte fuer diese Operationen haben.
|
|
|
|
Falls sich GraphQL-Mutationsnamen zwischen LLDAP-Versionen unterscheiden, muessen die Queries in `apps/api/src/lldap/lldap.service.ts` an die Zielversion angepasst werden.
|
|
|
|
## Application Info Log
|
|
|
|
Fachliche, sicherheitsrelevante und technische Aktivitaeten werden dauerhaft in `application_info_logs` gespeichert. Das bestehende `audit_events` bleibt unveraendert bestehen; jeder Aufruf von `AuditService.record(...)` erzeugt zusaetzlich einen strukturierten Info-Logeintrag. Ein globaler Interceptor erfasst abgeschlossene und fehlgeschlagene NestJS-HTTP-Requests ohne Request-Body und ohne Query-Parameter.
|
|
|
|
Wichtige Felder sind `action`, `category`, `outcome`, `actorType`, `actorId`, `userId`, `clientId`, `correlationId`, HTTP-Metadaten und bereinigter JSON-Kontext. Passwoerter, Tokens, Secrets, Cookies und Authorization-Daten werden entfernt; E-Mail-Adressen werden maskiert. Fehler beim Schreiben des Info-Logs werden auf dem Standardlogger ausgegeben und beeinflussen die protokollierte Aktion nicht.
|
|
|
|
Erfasst werden unter anderem Portal-Login und -Logout, fehlgeschlagene Logins, OIDC-Benutzerlogin, erfolgreiche Client-Autorisierung, Token-Grants, OIDC-Logout, Consent, Konto-, Passwort-, Registrierungs- und administrative Aenderungen. Die Migration `1721300000000-CreateApplicationInfoLogs.ts` muss in Produktion vor dem Deployment angewendet werden. Als Ausgangspunkt fuer die Aufbewahrung gelten 180 Tage; eine automatische Loeschung ist bewusst nicht aktiviert und muss mit der betrieblichen Audit-Policy abgestimmt werden.
|
|
|
|
## Produktions-Checkliste
|
|
|
|
- Externe MySQL-Datenbank angelegt und erreichbar.
|
|
- Datenbankschema/Migrationen fuer Produktion geklaert.
|
|
- Migration fuer `application_info_logs` angewendet.
|
|
- LLDAP Web/GraphQL und LDAP vom Container aus erreichbar.
|
|
- SMTP erreichbar und Absender korrekt gesetzt.
|
|
- `PUBLIC_WEB_URL` und `OIDC_ISSUER` auf die externe HTTPS-URL gesetzt.
|
|
- `OIDC_TRUST_PROXY=true`, wenn TLS am Reverse Proxy terminiert.
|
|
- `API_BASE_URL=/api` fuer Single-Container-Deployment gesetzt.
|
|
- Lange zufaellige Secrets gesetzt und gesichert.
|
|
- LLDAP-Gruppen fuer Admin-Rollen angelegt.
|
|
- Mindestens ein bestehender Admin-User ist Mitglied der benoetigten Gruppen.
|
|
- OIDC-Clients mit korrekten Redirect URIs angelegt.
|
|
- Reverse Proxy leitet `/.well-known/`, `/oidc/`, `/interaction/`, `/api/` und die Web-UI an den Container weiter.
|