Design: Zentrales Identity-Management + externer Zugriff (ohne Novabrands)¶
| Feld | Wert |
|---|---|
| Status | Design (genehmigt, bereit für Implementierungsplan) |
| Datum | 2026-06-29 |
| Betrifft | homelab-external, homeserver, homelab-documentation |
| Verwandt | RFC-002 (VPN-Redesign), RFC-003 (Authelia/LDAP), RFC-004 (VLANs) |
| Verhältnis zu RFC-003 | Aktualisiert RFC-003 — bestätigt die Identity-Wahl (Authelia + lldap auf Hetzner), klammert Novabrands aus, ergänzt die fehlende Exposition-Architektur und korrigiert die Forward-Auth-Annahme |
Ziel¶
Ein zentrales Benutzermanagement mit SSO für die wichtigsten Dienste und sicherer externer Zugriff auf die Homelab-Dienste — so einfach und robust wie möglich. Geltungsbereich:
- Homelab-Dienste (auf dem NUC): Immich, Jellyfin, Home Assistant, Grafana
- Hetzner-Dienste: Uptime Kuma, Healthchecks, Headplane, ntfy
- VPN-Login: Headscale via OIDC
Explizit nicht in Scope (vorerst): Novabrands (Nextcloud/OpenProject/Coder), SAML, RADIUS.
Kernentscheidungen¶
| Thema | Entscheidung | Begründung |
|---|---|---|
| IdP | Authelia + lldap auf Hetzner | Deckt OIDC (für Immich/HA/Grafana/Headscale) und Forward-Auth (für die Hetzner-Web-Tools) und LDAP (für Jellyfin/Infuse) in ~80 MiB ab. Kein Server-Upgrade nötig. Kleine Angriffsfläche. Migrationspfad zu Authentik bleibt dokumentiert offen. |
| Topologie | IdP auf Hetzner (immer online) | VPN-Login (Headscale) und Hetzner-Monitoring dürfen nicht von der Heim-Verfügbarkeit abhängen. Der zentrale IdP muss die verfügbarste Komponente sein. Authelia ist klein genug für den bestehenden Server (available ≈ 2,2 GiB). |
| Auth-Pfad vs. Daten-Pfad | entkoppelt über nativen OIDC | Immich/HA/Jellyfin brechen hinter Forward-Auth auf Mobilgeräten. Daher: nativer OIDC (App ist OIDC-Client), nur kurzer Login-Redirect zu Hetzner; der Daten-Traffic läuft direkt zum NUC. |
| Exposition | direkt über die UDM-Pro (Port-Forward 443 → NUC-Traefik) | Volle Bandbreite, kein Cloudflare-100-MB-Upload-Limit (das große Immich-Videos bricht), eigene Domain, dynamische IP via cloudflare-ddns. Bewusster Trade-off: Heim-IP per DNS sichtbar. |
| CrowdSec | Variante B (zentrale LAPI auf Hetzner, NUC als Remote-Agent über Tailnet) | Bans gelten netzwerkweit. NUC-Bouncer im Stream-Mode mit lokalem Cache → graceful degradation bei Tailnet-Ausfall. |
Architektur — Auth-Pfad und Daten-Pfad getrennt¶
INTERNET
┌───────────────────────┬───────────────────────────┐
Familie-Browser Familie-App (Immich/HA) VPN-Client (Tailscale)
│ Login-Redirect │ Login-Redirect │ OIDC-Login
▼ ▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ HETZNER (24/7, statische IP) — „Identity + Edge" │
│ Traefik ─┬─ Authelia (OIDC-Provider + Forward-Auth) │
│ ├─ lldap (LDAP-Backend: User + Gruppen) │
│ ├─ Headscale (VPN-Login via OIDC → Authelia) │
│ ├─ Uptime Kuma / Headplane / Healthchecks (FwdAuth) │
│ └─ ntfy (Token-Auth) │
│ identity-db (PostgreSQL) · CrowdSec ZENTRALE LAPI + AppSec│
└──────────────────────────────────────────────────────────────┘
▲ nur kurzer Login-Redirect (OIDC) — KEIN Daten-Traffic
Daten-Traffic läuft GETRENNT & DIREKT:
Familie/App ─► DynDNS-Domain ─► UDM-Pro (Port-Forward 443)
│ ① Default-Deny
│ ② GeoBlock (Country Restriction)
│ ③ IPS/Suricata (Emerging-Threats)
▼
NUC: Traefik
│ ④ CrowdSec Remote-Agent + Bouncer + AppSec
│ ⑤ TLS (Let's Encrypt)
▼
Immich · Jellyfin · HA · Grafana
(nativer OIDC-Login → Authelia@Hetzner;
Jellyfin: LDAP-Plugin → lldap@Hetzner über Tailnet)
Kernprinzip: Login geht überall zum schlanken, immer erreichbaren Authelia auf Hetzner. Die schweren Daten (Foto-Uploads, Streaming) gehen direkt zum NUC — kein Hetzner-Hop, kein Upload-Limit, volle Bandbreite.
Warum am NUC keine zusätzliche Angriffsfläche entsteht¶
- Kein Forward-Auth im NUC-Traefik → keine Authelia-Middleware, kein Verify-Endpunkt. Der NUC-Traefik bleibt ein reiner Router.
- Back-Channel (App@NUC → Authelia für Code→Token + JWKS) ist ein ausgehender HTTPS-Call zur ohnehin öffentlichen Authelia-URL → kein eingehender Port.
- Eingehend am NUC bleibt nur 443 (wie bisher), abgesichert via UDM-IPS + CrowdSec.
- Die gesamte Auth-Angriffsfläche (Login-Seite, OIDC-Endpunkte) liegt auf Hetzner, gehärtet hinter CrowdSec + MFA.
- Jellyfin-Ausnahme: Das LDAP-Plugin verbindet sich
Jellyfin@NUC → lldap@Hetzner(Port 3890/636) über das Tailnet, niemals öffentlich. lldap-LDAP wird nicht via Traefik exponiert (höchstens die lldap-Web-UI).
Komponenten¶
Neu auf Hetzner: authelia, lldap, identity-db (PostgreSQL), CrowdSec zentrale LAPI + Agent + Bouncer + AppSec.
Neu/erweitert auf dem NUC: CrowdSec Remote-Agent + Traefik-Bouncer + AppSec; Jellyfin-LDAP-Plugin; OIDC-Konfiguration der Dienste; UDM Port-Forward + GeoBlock + IPS.
Bestehend, nur angebunden: Headscale, Uptime Kuma, Healthchecks, Headplane, ntfy.
Pro-Dienst-Authentifizierung¶
| Dienst | Methode | Anbindung | Zu beachten |
|---|---|---|---|
| Immich | nativer OIDC | Client → Authelia; Mobile-Redirect-Override https://fotos.<domain>/api/oauth/mobile-redirect (Authelia akzeptiert app.immich:/// nicht direkt) |
Auto-Register aus; Gruppen-Gate am Authelia-Client (Immich filtert nicht selbst nach Gruppe); CrowdSec-Whitelist für /api/asset/... |
| Jellyfin | LDAP (nicht OIDC) | LDAP-Plugin → lldap über Tailnet | zentrales Passwort funktioniert in Infuse (Infuse kann kein OIDC/Quick-Connect); Such-Filter memberOf=homelab-user; LDAP-Pfad erzwingt kein MFA |
| Home Assistant | nativer OIDC (Web) | hass-oidc-auth (HACS) → Authelia |
Companion-App nutzt Long-Lived Access Token (OIDC bricht App); Gate homelab-admin |
| Grafana | nativer OIDC | generic_oauth → Authelia |
Authelia-4.39-Caveat: groups nicht im ID-Token → claims_policy setzen, sonst alle nur „Viewer" |
| Headscale | OIDC | oidc-Sektion → Authelia, PKCE |
allowed_groups: vpn-users |
| Uptime Kuma / Healthchecks / Headplane | Forward-Auth | Authelia-Middleware lokal auf Hetzner | Gate hetzner-admin / hetzner-user |
| ntfy | Token (bleibt) | optional Forward-Auth nur auf Web-UI | Mobile-Push braucht Token — nicht anfassen |
Muster: Alles Mobile-Relevante (Immich, HA) = nativer OIDC. Jellyfin = LDAP (wegen Infuse). Hetzner-Web-Tools ohne eigenes OIDC = Forward-Auth lokal auf Hetzner.
Sicherheit der direkten Exposition (Defense-in-Depth)¶
Reihenfolge, die ein eingehender Request durchläuft:
- UDM-Pro Firewall — Default-Deny, nur Port-Forward 443.
- UDM-Pro GeoBlock (Country Restriction, MaxMind) — droppt gesperrte Länder am Gateway. Kostenlos. Umgehbar (VPN), reduziert aber das Scan-Rauschen massiv.
- UDM-Pro IPS (Suricata + Emerging-Threats; ET-OPEN kostenlos, ET-PRO via CyberSecure ~7,5 €/Monat) — Signatur-basiert. Grenze: sieht den HTTPS-Inhalt nicht.
- NUC-Traefik TLS (Let's Encrypt) — ab hier ist der Request entschlüsselt.
- CrowdSec @ NUC-Traefik — IP-Reputation (Community-Blocklist) + Verhaltens-Szenarien (aus Traefik-Logs) + AppSec-WAF (Virtual Patching, SQLi/XSS/Traversal). Fängt die L7-Angriffe, die die UDM-IPS hinter HTTPS nicht sieht.
- Nativer OIDC-Login + MFA (Authelia @ Hetzner) — die eigentliche Zugriffskontrolle.
Warum UDM-IPS und CrowdSec sich ergänzen: UDM = Netzwerk-L3/L4 vor TLS über alle Protokolle (Signaturen). CrowdSec = Anwendung-L7 nach TLS (Community-CTI + Verhalten + WAF). Keiner sieht, was der andere sieht.
Caveats: CrowdSec braucht echte Client-IPs (forwardedHeadersTrustedIPs/clientTrustedIPs) — bei direkter Exposition trivial. CrowdSec-Bouncer ist standardmäßig fail-open (final festzulegen). Immich braucht eine CrowdSec-Whitelist (Galerie-Scroll triggert sonst „Enumeration"-Ban).
CrowdSec — Variante B (geteilte Intelligenz)¶
- Zentrale LAPI auf Hetzner (immer online) + lokaler Agent (Hetzner-Traefik-Logs) + Hetzner-Bouncer + AppSec.
- NUC = Remote-Agent: liest NUC-Traefik-Logs, registriert sich über das Tailnet an der Hetzner-LAPI; NUC-Bouncer + AppSec fragen die Hetzner-LAPI.
- Ein Ban auf einem Host gilt auf beiden (~60 s über Stream-Cache-Refresh).
- LAPI bindet nur auf die Tailnet-IP (100.x), niemals öffentlich/über Traefik.
- Robustheit: NUC-Bouncer im Stream-Mode mit lokalem Cache → bei Tailnet-Ausfall arbeitet der NUC mit der letzten gecachten Blockliste weiter (danach fail-open). Betrifft nur die Block-Frische, nicht Daten-Pfad oder Login.
User-Modell, Gruppen & MFA (in lldap)¶
Users: robin (Admin), lars (Partner). Keine weiteren Mitglieder vorerst. Single Source of Truth.
| Gruppe | Zweck | Mitglieder |
|---|---|---|
homelab-admin |
Vollzugriff (HA, Grafana, Immich-Admin) | robin |
homelab-user |
Immich + Jellyfin (Haushalt) | robin, lars |
vpn-users |
VPN-Login (Headscale OIDC) | robin |
hetzner-admin |
Headplane, Healthchecks | robin |
hetzner-user |
Uptime Kuma, ntfy | robin |
Durchsetzung über Authelia (Gruppen-Gate pro OIDC-Client / Forward-Auth-Regel) bzw. LDAP-Such-Filter (Jellyfin).
MFA (Authelia, Pflicht): TOTP + WebAuthn/Passkeys, policy: two_factor. Bootstrapping: initial one_factor für die Erst-Registrierung, danach umschalten. Jellyfin/Infuse läuft über LDAP ohne erzwingbares MFA (Passwort-only) — akzeptabel, da niedrig-sensibel; die sensiblen Dienste (Immich/HA/Grafana) laufen über OIDC mit MFA.
Secrets & Backup¶
- Secrets: Authelia/lldap-Geheimnisse (JWT-, Session-, Storage-Encryption-Key, DB-Passwort), OIDC-Client-Secrets, LDAP-Service-Account → über das etablierte Secrets-Pattern des jeweiligen Repos (Infisical/Env je Host).
- Backup:
identity-db(DBsauthelia+lldap) → bestehender Borgmatic-Zyklus (pg_dump), wie die Headscale-DB. RPO < 24 h. - Break-Glass: lokale Admin-Accounts je Dienst (Immich/HA/Grafana/Jellyfin) als Notfall-Zugang behalten (in Proton Pass).
Betroffene Repos¶
| Repo | Änderung |
|---|---|
| homelab-external | Authelia + lldap + identity-db deployen; Headscale-OIDC; Forward-Auth für Hetzner-Tools; CrowdSec zentrale LAPI |
| homeserver | OIDC für Immich/HA/Grafana; Jellyfin-LDAP-Plugin; NUC-Traefik CrowdSec Remote-Agent + Bouncer + AppSec; UDM Port-Forward + GeoBlock + IPS; DNS |
| homelab-documentation | RFC-003 auf dieses Design aktualisieren; LikeC4-Modell/Diagramme |
Rollout-Phasen¶
- Identity-Stack auf Hetzner: lldap + Authelia + identity-db; User/Gruppen/MFA anlegen; Test über Authelia-Portal.
- Headscale-OIDC anbinden; VPN-Login testen.
- Hetzner-Tools auf Forward-Auth (Uptime Kuma, Healthchecks, Headplane).
- CrowdSec Variante B: zentrale LAPI auf Hetzner; NUC-Remote-Agent + Bouncer über Tailnet.
- Exposition: UDM Port-Forward + GeoBlock + IPS; NUC-Traefik CrowdSec; dann Dienst für Dienst OIDC/LDAP — Immich → Jellyfin/Infuse → HA → Grafana — je mit Web- und Mobile-Test.
Offene Detail-Punkte (für Implementierungsplan)¶
- Versionen pinnen (Authelia, lldap, CrowdSec; Image-Tags recherchieren).
- CrowdSec-Whitelist-Ausdrücke für Immich (
/api/asset/upload,/api/asset/thumbnail/...). - fail-open vs. fail-closed für den CrowdSec-Bouncer final festlegen.
- Authelia-4.39
claims_policyfür Grafana-Gruppen (sonst alle „Viewer"). - GeoBlock-Allow-Liste festlegen (z. B. DE/AT/CH).
- Jellyfin-LDAP-Plugin gegen lldap testen (Bind, Such-Filter, Infuse-Login).
- DNS-Einträge:
auth.<domain>(Hetzner), Homelab-Dienste auf Heim-IP viacloudflare-ddns; Cloudflare-Orange-Cloud für Immich vermeiden (sonst 100-MB-Limit). - ntfy: prüfen, ob Forward-Auth auf der Web-UI ohne Bruch des Mobile-Push möglich ist.
Risiken¶
| Risiko | Mitigation |
|---|---|
| Hetzner-Ausfall = kein neuer Login | Break-Glass-Accounts je Dienst; laufende Sessions/Token bleiben gültig (OIDC-Refresh, LDAP-Cache) |
| Heim-IP per DNS sichtbar | UDM-IPS + GeoBlock + CrowdSec + OIDC-MFA als Defense-in-Depth; Immich-Zero-Day-Restrisiko an MFA-Login abgefangen |
| Immich-CrowdSec-Fehlalarm (Scroll) | gezielte Whitelist für /api/asset/... |
| Companion-Apps brechen hinter Auth | HA-Companion via Long-Lived Token; Immich-App via nativem OIDC (kein Forward-Auth); Jellyfin via Infuse/LDAP |
| Tailnet-Ausfall → CrowdSec-Bans am NUC veralten | Stream-Mode-Cache + fail-open; nur Block-Frische betroffen |
Verworfene Alternativen (mit Begründung)¶
- Pocket ID (IdP): minimal-einfachster IdP, aber Passkey-only und kein Forward-Auth → Hetzner-Tools bräuchten Zusatz-
oauth2-proxy; und kein LDAP-Server → kein zentrales Jellyfin/Infuse-Login. Für den vollen Scope unpassend. - Authentik (IdP): mächtiger (OIDC+SAML+LDAP-Outpost, Web-UI), aber ~1–2 GiB RAM → Hetzner-Server müsste auf die 8-GiB-Klasse (CX32) hoch; mehr Wartung + Angriffsfläche. Für 2 Personen overkill. Bleibt dokumentierter Migrationspfad.
- Cloudflare Tunnel für Immich: hartes 100-MB-Upload-Limit (Free/Pro) → große Videos brechen. Immich-Chunked-Upload (TUS) nur Roadmap.
- Tailscale Funnel für Immich: keine eigene Domain, Bandbreiten-Cap, offener Android-Upload-Bug.
- Hetzner-Edge (Service-Traffic über Tailscale durch Hetzner): verschleiert die Heim-IP, aber leitet allen Medien-Traffic über Hetzner (Latenz/Last) — vom Nutzer bewusst verworfen zugunsten des direkten Pfads.
- IdP auf dem NUC: würde VPN-Login und Hetzner-Monitoring von der Heim-Verfügbarkeit abhängig machen (zirkuläre Abhängigkeit im Notfall) und eine zusätzliche öffentliche Auth-Fläche zuhause schaffen.