Zum Inhalt

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:

  1. UDM-Pro Firewall — Default-Deny, nur Port-Forward 443.
  2. UDM-Pro GeoBlock (Country Restriction, MaxMind) — droppt gesperrte Länder am Gateway. Kostenlos. Umgehbar (VPN), reduziert aber das Scan-Rauschen massiv.
  3. UDM-Pro IPS (Suricata + Emerging-Threats; ET-OPEN kostenlos, ET-PRO via CyberSecure ~7,5 €/Monat) — Signatur-basiert. Grenze: sieht den HTTPS-Inhalt nicht.
  4. NUC-Traefik TLS (Let's Encrypt) — ab hier ist der Request entschlüsselt.
  5. 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.
  6. 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 (DBs authelia + 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

  1. Identity-Stack auf Hetzner: lldap + Authelia + identity-db; User/Gruppen/MFA anlegen; Test über Authelia-Portal.
  2. Headscale-OIDC anbinden; VPN-Login testen.
  3. Hetzner-Tools auf Forward-Auth (Uptime Kuma, Healthchecks, Headplane).
  4. CrowdSec Variante B: zentrale LAPI auf Hetzner; NUC-Remote-Agent + Bouncer über Tailnet.
  5. 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_policy fü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 via cloudflare-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.