No description
  • Python 71.5%
  • Shell 28.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Toni Schreiber 9b8e2a505c
Some checks failed
deploy / validate (push) Has been cancelled
deploy / deploy (push) Has been cancelled
validate / validate (push) Has been cancelled
nextcloud install
2026-10-16 20:27:10 +02:00
.gitea/workflows init 2026-10-16 16:37:17 +02:00
blueprints Theme: gelber Akzent mit schwarzer Schrift, Portainer-Doku 2026-10-16 17:35:17 +02:00
custom-templates init 2026-10-16 16:37:17 +02:00
media/public init 2026-10-16 16:37:17 +02:00
scripts init 2026-10-16 16:37:17 +02:00
.env.example Theme: gelber Akzent mit schwarzer Schrift, Portainer-Doku 2026-10-16 17:35:17 +02:00
.gitignore Theme: gelber Akzent mit schwarzer Schrift, Portainer-Doku 2026-10-16 17:35:17 +02:00
.yamllint init 2026-10-16 16:37:17 +02:00
docker-compose.yml fix für portainer - ordner Fehler 2026-10-16 16:51:34 +02:00
nextcloud-install-oidc.md nextcloud install 2026-10-16 20:27:10 +02:00
README.md Theme: gelber Akzent mit schwarzer Schrift, Portainer-Doku 2026-10-16 17:35:17 +02:00

Authentik – zentrales Login (SSO) für FKF ev

Selbst gehosteter Authentik-Server als zentrales Login-System des Vereins. Die gesamte Konfiguration (Branding, Scopes, angebundene Anwendungen) liegt als Blueprints in diesem Repository und wird automatisch angewendet. Gepinnte Version: 2026.8.4.

Architektur

Internet ──TLS──> externer Reverse Proxy (nginx) ──HTTP──> 127.0.0.1:9000 ──> server ──┐
                                                                               worker ──┼──> postgresql
                                                                                        │
   ./blueprints (read-only) ──> /blueprints/custom     ./media ──> /data/media          │
   ./custom-templates ──> /templates                   Volume "database" <──────────────┘
Dienst Aufgabe
server Weboberfläche, Login-Flows, OIDC-Endpunkte, API
worker Hintergrundaufgaben: Blueprints anwenden, E-Mails versenden, Aufräumen
postgresql Datenbank (einziger Datenspeicher; Redis wird in dieser Version nicht mehr benötigt)

Der Reverse Proxy terminiert TLS. Authentik stellt nur HTTP bereit, standardmäßig an 127.0.0.1 gebunden (AUTHENTIK_BIND_ADDRESS/AUTHENTIK_PORT_HTTP in .env).

Repository-Struktur

Pfad Inhalt
docker-compose.yml Stack (postgresql, server, worker)
.env.example Vorlage aller Variablen; echte .env nur auf dem Server
blueprints/00-branding.yaml Brand: Titel, Logo, Favicon, CSS
blueprints/10-scope-mappings.yaml OIDC-Scope verein (Claim mitgliedsnummer)
blueprints/apps/ Eine Datei pro angebundener Anwendung; example-app.yaml = Vorlage
blueprints/apps/nextcloud.yaml Nextcloud (OIDC), siehe Abschnitt „Nextcloud“
media/public/ Logo (hell/dunkel) und Favicon
custom-templates/ Eigene E-Mail-Templates (derzeit leer)
scripts/generate-secrets.sh Erzeugt .env mit Zufallswerten
scripts/check-blueprints.py Offline-Prüfung der Blueprints
scripts/validate.sh Alle Prüfungen (lokal = CI)
.gitea/workflows/ validate.yml (Push/PR), deploy.yml (nur master)

Abweichungen von Standardanleitungen (Authentik 2026.8)

  • Dateipfade für Logo/Favicon: Absolute Pfade wie /media/public/logo.svg werden in 2026.8 abgelehnt. Authentik speichert Dateien unter /data/media/public/ und erwartet in Blueprints nur den Dateinamen (branding_logo: "fkf-logo-%(theme)s.png"). %(theme)s wird durch light/dark ersetzt.
  • Eigenes CSS: Das Brand-Feld branding_custom_css existiert in 2026.8.4, der „Alternativweg“ (Custom-CSS-Datei) ist daher nicht nötig. Selektoren (ak-flow-executor::part(main), --pf-global--primary-color--*) wurden gegen den Quellcode der Version geprüft; Authentik hat aber keine stabile CSS-API – nach Updates die Login-Seite kurz ansehen.
  • Flow-Felder: flow_invalidation (Brand) und invalidation_flow (Provider) heißen in dieser Version so.
  • Docker-Socket/certs: Gegenüber dem offiziellen Compose entfernt (nur für Outposts per Docker nötig).
  • Logo: Das Original ist schwarz auf transparent. Für den Dunkelmodus gibt es fkf-logo-dark.png mit weißem Schriftzug; das Favicon wurde aus dem Badge erzeugt. Bei neuem Logo beide Varianten ersetzen.

Ersteinrichtung

Voraussetzungen: Linux-Server mit Docker + Compose-Plugin, Git, ein Reverse Proxy mit TLS für auth.ts-q.de.

git clone <repo-url> /opt/authentik && cd /opt/authentik
./scripts/generate-secrets.sh      # erzeugt .env (überschreibt nie eine vorhandene)
$EDITOR .env                       # SMTP-Zugang, Admin-E-Mail und ggf. Port/Bind-Adresse eintragen
docker compose up -d
docker compose logs -f server worker
  1. Reverse Proxy einrichten (siehe unten), DNS für auth.ts-q.de setzen.
  2. Admin-Konto: Es gibt zwei Wege.
    • Vorab konfiguriert (empfohlen): generate-secrets.sh setzt AUTHENTIK_BOOTSTRAP_PASSWORD auf ein Zufallspasswort. Beim allerersten Start legt Authentik damit den Benutzer akadmin an (E-Mail aus AUTHENTIK_BOOTSTRAP_EMAIL). Anmelden unter https://auth.ts-q.de/ mit akadmin und dem Passwort aus der .env. Danach Passwort in der Oberfläche ändern, MFA einrichten und die AUTHENTIK_BOOTSTRAP_*-Zeilen aus der .env auf dem Server entfernen. Die Initial-Setup-Seite ist dann gesperrt („Zugriff verweigert“), niemand kann das System übernehmen. Die Variablen wirken nur beim ersten Anlegen des Benutzers. Existiert akadmin schon, ändern spätere Werte nichts. Statt des Klartext-Passworts geht auch AUTHENTIK_BOOTSTRAP_PASSWORD_HASH (Django-Hash-Format). Optional legt AUTHENTIK_BOOTSTRAP_TOKEN einen API-Token für den Admin an.
    • Per Initial-Setup: Die Bootstrap-Zeilen aus der .env entfernen, vor dem ersten Start https://auth.ts-q.de/if/flow/initial-setup/ aufrufen und dort das Passwort für akadmin setzen. Das sofort tun, die Seite ist bis dahin für jeden offen.
  3. In der Admin-Oberfläche unter System → Einstellungen die Base URL auf https://auth.ts-q.de setzen (wird ab Authentik 2026.11 Pflicht).
  4. Prüfen, dass die Blueprints gelaufen sind (siehe „Blueprints anwenden und prüfen“).
  5. AUTHENTIK_SECRET_KEY sichern (Passwortmanager) und nie ändern.

Das erste Hochfahren dauert 1–2 Minuten (Datenbank-Migration). Schlägt ein Blueprint beim allerersten Start fehl, weil ein mitgelieferter Standard-Flow noch nicht angelegt war, hilft docker compose restart worker.

Externer Reverse Proxy

Authentik braucht: den originalen Host, X-Forwarded-Proto/X-Forwarded-For und WebSocket-Support (Upgrade/Connection; wird u. a. für Outposts und Live-Updates genutzt). Ab 2026.8 vertraut Authentik diesen Headern nur, wenn die Verbindung aus einem vertrauenswürdigen Netz kommt (AUTHENTIK_LISTEN__TRUSTED_PROXY_CIDRS; Standard: private Netze inkl. Docker-Bridges, siehe .env.example). Liegt der Proxy in einem anderen Netz, dieses eintragen – sonst erkennt Authentik https nicht und Weiterleitungen/Cookies verhalten sich falsch.

Beispiel nginx:

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl;
    http2 on;
    server_name auth.ts-q.de;

    ssl_certificate     /etc/letsencrypt/live/auth.ts-q.de/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.ts-q.de/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:9000;   # AUTHENTIK_BIND_ADDRESS:AUTHENTIK_PORT_HTTP
        proxy_http_version 1.1;

        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

server {
    listen 80;
    server_name auth.ts-q.de;
    return 301 https://$host$request_uri;
}

Läuft der Proxy auf einem anderen Rechner: AUTHENTIK_BIND_ADDRESS auf die interne IP des Servers setzen und den Port per Firewall nur für den Proxy öffnen.

Portainer

Läuft der Stack als Portainer-Stack (z. B. aus dem Git-Repository), gibt es zwei Besonderheiten.

1. Variablen: Die .env liegt nicht im Repo. In Portainer lädst du sie im Stack-Formular („Load variables from .env file“). Portainer nutzt die Werte für ${…} in der Compose-Datei und legt sie als stack.env im Stack-Ordner ab. Die Compose-Datei liest deshalb .env und stack.env, beide sind optional. Nach dem ersten Start prüfen, ob die Container die Werte haben: docker exec <worker-container> printenv NEXTCLOUD_CLIENT_ID (kommt nichts zurück, fehlen sie und die App-Blueprints schlagen fehl).

2. Bind-Mounts: Portainer checkt das Repo nach /data/compose/<id> aus. Das ist ein Pfad im Portainer-Container. Der Docker-Daemon sucht ./blueprints, ./media und ./custom-templates aber auf dem Host. Ohne Anpassung kommt: Bind mount failed: '/data/compose/<id>/media' does not exist. Lösung: PROJECT_DIR (siehe .env.example) auf den Host-Pfad des Repos setzen. Zwei Wege:

  • Stack-Ordner von Portainer verwenden (kein zusätzlicher Klon nötig). Läuft Portainer mit einem benannten Volume portainer_data, ist der Host-Pfad $(docker volume inspect portainer_data --format '{{.Mountpoint}}')/compose/<id>, meist PROJECT_DIR=/var/lib/docker/volumes/portainer_data/_data/compose/<id>. Die <id> steht in der Fehlermeldung bzw. in der URL des Stacks. Sie ändert sich, wenn der Stack neu angelegt wird. Dann PROJECT_DIR nachziehen. Bei Portainer-Updates aus Git bleiben die Dateien im Ordner aktuell. (Nicht auf allen Setups getestet.)
  • Eigener Klon auf dem Host: git clone <repo-url> /opt/authentik, dann PROJECT_DIR=/opt/authentik. Der Klon muss bei Änderungen per git pull aktuell gehalten werden (z. B. durch deploy.yml). Blueprint-Änderungen greifen erst nach dem git pull.

Die Business Edition von Portainer kennt zusätzlich die Option „Enable relative path volumes“, die dasselbe löst. Außerhalb von Portainer (docker compose im Projektordner) bleibt PROJECT_DIR leer.

Neue Anwendung anbinden

  1. blueprints/apps/example-app.yaml nach blueprints/apps/<app>.yaml kopieren.
  2. In der Kopie alle mit ANPASSEN markierten Stellen ändern: metadata.name, Provider-name, slug, redirect_uris, meta_launch_url, Namen der Env-Variablen. Das Label blueprints.goauthentik.io/instantiate: "false" entfernen (es verhindert das Anwenden der Vorlage).
  3. Client-Typ wählen:
    • confidential – App mit Backend (z. B. Yii2): Client-Secret wird serverseitig verwendet.
    • public + PKCE – reine Browser-/Mobile-Apps (z. B. React-SPA): client_type: public, client_secret weglassen, in der App PKCE (S256) aktivieren.
  4. In .env auf dem Server die Variablen ergänzen (<APP>_CLIENT_ID, <APP>_CLIENT_SECRET; Secret z. B. mit openssl rand -hex 32). Ebenfalls als Platzhalter in .env.example dokumentieren. Die Variablen stehen nur auf dem Server; im Repo steht nie ein Secret.
  5. ./scripts/validate.sh ausführen, committen, nach master pushen. Die .env muss vorher auf dem Server ergänzt sein; docker compose up -d erstellt server und worker bei geänderter Umgebung neu.
  6. In der App eintragen (Endpunkte findet man in Authentik am Provider unter „OpenID-Konfigurations-URL“):
    • Issuer/Discovery: https://auth.ts-q.de/application/o/<slug>/.well-known/openid-configuration
    • Scopes: openid profile email verein
    • Claim mitgliedsnummer steht (bei include_claims_in_id_token: true) im ID-Token und im Userinfo.
  7. Die Mitgliedsnummer pflegt man am Nutzer unter Verzeichnis → Nutzer → Attribute: mitgliedsnummer: "12345".

Nextcloud

Angebunden ist https://nextcloud.ts-q.net per OpenID Connect. Auf der Nextcloud-Seite dient dafür die App „OpenID Connect user backend“ (user_oidc).

Was in Authentik eingerichtet ist

Alles steckt in blueprints/apps/nextcloud.yaml (Provider + Application) und blueprints/10-scope-mappings.yaml (Scope nextcloud). Die Zugangsdaten kommen aus der .env (NEXTCLOUD_CLIENT_ID, NEXTCLOUD_CLIENT_SECRET, von generate-secrets.sh erzeugt).

Einstellung Wert Grund
Provider-Name Nextcloud (OIDC) eindeutiger Name, darüber wird er wiedergefunden
Client-Typ confidential Nextcloud hat ein Backend und hält das Secret geheim
Redirect-URI (Login) https://nextcloud.ts-q.net/apps/user_oidc/code, strikt fester Rücksprungpfad der App user_oidc
Redirect-URI (Post Logout) https://nextcloud.ts-q.net, strikt Ziel nach der Abmeldung
Back-Channel-Logout https://nextcloud.ts-q.net/apps/user_oidc/backchannel-logout/authentik Abmeldung in Authentik beendet auch die Nextcloud-Sitzung
Grant-Types authorization_code, refresh_token Standard-Login mit Code-Austausch
sub user_uuid stabile, nicht erratbare Kennung (Empfehlung der Authentik-Doku)
Signatur RS256 mit dem Zertifikat „authentik Self-signed Certificate“ Tokens sind für Nextcloud prüfbar
Flows default-provider-authorization-implicit-consent, default-provider-invalidation-flow kein Zustimmungsdialog
Scopes openid, email, profile, nextcloud siehe unten
Application Name Nextcloud, Slug nextcloud Slug steckt in der Discovery-URL

Scope nextcloud (Mapping „FKF: Scope 'nextcloud'“) liefert zusätzlich:

  • user_id: der Benutzername, damit Nextcloud-Konten lesbar heißen (statt UUID).
  • quota: Speicherplatz, gepflegt als Attribut nextcloud_quota an der Gruppe oder am Nutzer (z. B. 5 GB, none; der Nutzerwert gewinnt). Ohne Attribut gilt Nextclouds Standard.
  • Gruppen kommen nicht aus diesem Scope, sondern aus dem Standard-Scope profile (Claim groups, direkte Gruppen des Nutzers). Würde der Nextcloud-Scope sie zusätzlich liefern, stünde jede Gruppe doppelt im Token (Authentik hängt Listen aus mehreren Mappings aneinander).

Wer sich anmelden darf: aktuell alle Authentik-Nutzer. Zur Einschränkung am Application-Eintrag in der Oberfläche eine Gruppen-Bindung („Policy / Group / User Bindings“) setzen.

Was du in Nextcloud einträgst

  1. App OpenID Connect user backend installieren (Apps → Sicherheit).
  2. Verwaltungseinstellungen → OpenID Connect → neuen Provider anlegen:
Feld in Nextcloud Wert
Identifier authentik (muss zur Back-Channel-Logout-URL oben passen)
Client ID / Client secret NEXTCLOUD_CLIENT_ID / NEXTCLOUD_CLIENT_SECRET aus der .env auf dem Server
Discovery endpoint https://auth.ts-q.de/application/o/nextcloud/.well-known/openid-configuration
Scope openid email profile nextcloud
User ID mapping user_id
Display name mapping name
Email mapping email
Quota mapping quota
Groups mapping groups
Use group provisioning an
Use unique user ID aus, damit Konten user_id (Benutzername) heißen und bestehende Nextcloud-Konten mit gleichem Namen übernommen werden
  1. Optional: Anmeldeseite von Nextcloud überspringen und direkt zu Authentik leiten: sudo -u www-data php occ config:app:set --type=string --value=0 user_oidc allow_multiple_user_backends (Danach geht lokaler Nextcloud-Login nur noch über https://nextcloud.ts-q.net/login?direct=1.)

Hinweise:

  • Die Redirect-URIs sind strikt. Ändert sich die Nextcloud-Adresse, müssen redirect_uris und logout_uri in blueprints/apps/nextcloud.yaml sowie meta_launch_url angepasst werden.
  • Nextcloud muss auth.ts-q.de per HTTPS erreichen (Discovery, Token- und Userinfo-Abruf laufen serverseitig).
  • Ein Nextcloud-Admin ergibt sich über die Nextcloud-Gruppe admin; eine gleichnamige Authentik-Gruppe wird per Gruppen-Provisioning dorthin übernommen (nicht getestet).

Was geprüft wurde (lokale Authentik-Testinstanz, ohne echte Nextcloud)

  • Blueprint wird fehlerfrei angewendet; Discovery liefert RS256, nextcloud als Scope, Back-Channel-Logout unterstützt.
  • Kompletter Ablauf Login → Code → Token-Endpunkt: ID-Token enthält sub (UUID), email, name, user_id, quota, groups.
  • Falsches Client-Secret wird abgelehnt, eine nicht eingetragene Redirect-URI ebenfalls.
  • Nicht getestet: Zusammenspiel mit einer echten Nextcloud (App-Einstellungen, Gruppen-Provisioning) und der Back-Channel-Logout.

Blueprints anwenden und prüfen

Authentik überwacht /blueprints (hier: ./blueprints → /blueprints/custom, nur lesbar) rekursiv: neue oder geänderte *.yaml-Dateien werden automatisch angewendet, zusätzlich läuft jede Stunde ein Abgleich. Ein Blueprint wird in einer Transaktion angewendet – bei einem Fehler bleibt alles unverändert. Die Blueprints sind idempotent (Objekte werden über identifiers wiedergefunden) und überschreiben manuelle Änderungen in der GUI beim nächsten Anwenden.

Prüfen:

  • Admin-Oberfläche → Anpassung → Blueprints (Menü kann je nach Version leicht abweichend heißen): Status pro Blueprint (successful / error) und Zeitpunkt der letzten Anwendung.
  • Logs: docker compose logs worker | grep -i blueprint
  • Bei Fehler: Meldung dort lesen (meist Tippfehler in Feldnamen oder ein fehlendes Objekt bei !Find), Datei korrigieren, pushen – oder in der Oberfläche beim Blueprint auf „Anwenden“ klicken.
  • Vor dem Push: ./scripts/validate.sh (yamllint, YAML-/Tag-Prüfung, Prüfung gegen das offizielle Schema der Version, docker compose config).

CI/CD (Gitea Actions)

  • validate.yml (Push und Pull Request): ./scripts/validate.sh. Der Runner braucht Docker mit Compose-Plugin und Internetzugang (goauthentik.io für das Schema).
  • deploy.yml (nur master): erst Validierung, dann per SSH: git pull --ff-only, docker compose pull, docker compose up -d. Die .env wird nie aus der CI geschrieben.

Gitea-Secrets: DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY, DEPLOY_PATH, DEPLOY_KNOWN_HOSTS (Ausgabe von ssh-keyscan -p <port> <host>), optional DEPLOY_PORT. Auf dem Server muss das Repo mit einem lesenden Zugang (z. B. Deploy-Key) geklont sein und .env existieren.

Update von Authentik

AUTHENTIK_TAG in .env.example und in der .env auf dem Server erhöhen, vorher die Release Notes lesen (nur eine Minor-Version auf einmal), Backup der Datenbank machen, dann deployen.

Datenhaltung

  • Nutzerdaten und Nutzer-Attribute liegen unverschlüsselt in PostgreSQL (Volume database).
  • Sensible Fachdaten (z. B. Bankverbindungen, Adressen über das Nötigste hinaus) gehören nicht in Authentik, sondern in die Fachanwendung, verknüpft über die Mitgliedsnummer (mitgliedsnummer).
  • Empfehlung: Verschlüsselung at rest auf Host-Ebene (LUKS) für die Platte mit dem Docker-Datenverzeichnis und den Backups.
  • Backups: docker compose exec -T postgresql pg_dump -U authentik authentik | gzip > backup.sql.gz (verschlüsselt ablegen) und .env separat im Passwortmanager sichern.

Geplant

Noch nicht umgesetzt, nur dokumentiert:

  • Import bestehender Mitglieder samt Passwort-Hashes über die Authentik-API, damit niemand sein Passwort neu setzen muss.
    • Authentik speichert Passwörter im Django-Hash-Format (<algorithmus>$<parameter>$<hash>).
    • Bcrypt-Hashes aus PHP/Yii2 beginnen mit $2y$; Django erwartet $2b$. Beim Import das Präfix $2y → $2b umschreiben und im Django-Format ablegen (bcrypt$$2b$…).
    • Dafür muss der passende Hasher in Authentik aktiviert sein (Django-PASSWORD_HASHERS); der Hash sollte beim ersten erfolgreichen Login auf den Standard-Hasher hochgestuft werden (Django-Verhalten, für Authentik zu verifizieren).
    • Hinweis: Das Benutzermodell kennt in 2026.8.4 ein Attribut password_hash (der mitgelieferte Bootstrap-Blueprint nutzt es für AUTHENTIK_BOOTSTRAP_PASSWORD_HASH). Das ist der erste Anlaufpunkt für den Import.
    • Offen/zu prüfen vor der Umsetzung: ob die API in 2026.8 ein fertiges Hash-Feld beim Anlegen akzeptiert und wie der Hasher aktiviert wird.