Scrublord MacBad b1247b4720 backup old wiki
2026-05-14 23:34:59 +02:00

32 KiB
Raw Blame History

Invite-basierte Selbstregistrierung mit MAS in ESS Community unter Ansible und FluxCD

Zusammenfassung

Für ein entity["company","Element","matrix software company"]-basiertes ESS-Community-Deployment mit aktivem Matrix Authentication Service ist eine invite-basierte Selbstregistrierung ohne Authentik sauber umsetzbar. Der belastbare Weg ist: MAS-Registrierung aktivieren, Registrierungstoken erzwingen, Tokens über die MAS Admin API erzeugen, und Standard-Onboarding über Synapse auto_join_rooms abbilden. In MAS-/MSC3861-Setups sind die alten Synapse-Mechanismen für Registration Tokens bzw. Shared-Secret-Registration nicht mehr der richtige Pfad: Die Synapse-Registration-Token-API ist in diesem Modus deaktiviert, ebenso die Shared-Secret-Registration-API; die offizielle Automationsoberfläche für externe Tools ist stattdessen die MAS Admin API. citeturn1view2turn20view1turn26search11turn26search5turn35view0

Für die Zuweisung zu Räumen und Spaces gilt: MAS-Claims sind für Identitätsattribute gedacht, nicht für Raum-/Space-Mitgliedschaften. Der robuste Standard ist daher eine Zweiteilung: allgemeine Onboarding-Räume und Spaces über Synapse auto_join_rooms, und feingranulare, zielgruppenspezifische Mitgliedschaften über einen kleinen Post-Registration-Provisioner oder ein Synapse-Modul über on_user_registration bzw. on_user_login. Gruppen/Communities sind heute kein sinnvolles Ziel mehr, weil Synapse diese Funktion deprecated und anschließend entfernt hat; für aktuelle Deployments solltest du konsequent mit Spaces + Rooms arbeiten. citeturn16view0turn16view1turn16view2turn34view0turn35view0turn17search0turn17search2

Für GitOps empfehle ich eine klare Trennung zwischen statischer Konfiguration und operativem Invite-Zustand. Statisch in Git/SOPS/Flux gehören: MAS-Account-Settings, SMTP-Daten, der MAS-Admin-OAuth-Client und optionale Onboarding-Policies. Kurzlebige Einmal-Tokens sollten dagegen in der Regel nicht als dauerhafter Desired State in Git geführt werden, sondern bei Bedarf über die MAS Admin API erzeugt, per E-Mail verschickt und nur optional als Audit-Record abgelegt werden. Das passt deutlich besser zu FluxCD als das “GitOpsen” flüchtiger Einladungen. Die optionale LiveKit-/Element-Call-Schicht ist für den Registrierungsfluss architektonisch unabhängig. citeturn20view1turn23view0turn23view3turn25search0turn25search13

Zielbild und Architektur

Die offizielle ESS-Dokumentation unterstützt zusätzliche Konfiguration für Synapse, MAS, Element Web und MatrixRTC/LiveKit jeweils über additional-Blöcke. MAS läuft dabei neben Synapse und spricht mit Synapse über den dedizierten matrix_authentication_service-Mechanismus; Synapse 1.136+ führt diese Integration als stabile Konfiguration. Für dein Ziel bedeutet das: ESS/Helm liefert die Plattform, MAS erzwingt die Token-basierte Registrierung, Synapse übernimmt Mitgliedschaften, und Element Web kann optional eine eigene Landing-/Invite-Seite anzeigen. citeturn1view2turn1view5turn31view1turn35view0turn24search0

flowchart TB
    subgraph GitOps["GitOps-Steuerung"]
      A[Ansible]
      F[FluxCD]
      G[Git Repository]
      S[SOPS Secrets]
      A --> G
      S --> G
      F --> G
    end

    subgraph K8s["Kubernetes / ESS Helm"]
      HR[HelmRelease matrix-stack]
      EW[Element Web]
      MAS[Matrix Authentication Service]
      SYN[Synapse]
      PG[(PostgreSQL)]
      SMTP[SMTP]
      MOD[Optional: Post-Registration Provisioner / Synapse-Modul]
      LK[Optional: LiveKit / Element Call]
    end

    G --> HR
    HR --> EW
    HR --> MAS
    HR --> SYN
    HR --> PG
    HR --> LK

    MAS --> PG
    SYN --> PG
    MAS --> SMTP
    MAS <--> SYN
    EW --> MAS
    EW --> SYN

    A -->|MAS Admin API| MAS
    A -->|E-Mail Einladung| User[Neuer Benutzer]
    User -->|Invite-Link + Registration Token| EW
    User -->|Registrierung| MAS
    MAS -->|User-Provisionierung| SYN
    SYN -->|auto_join_rooms| User
    MOD -->|join/invite per Synapse Admin API| SYN

Die praktikabelste Zielarchitektur ist deshalb: Invite-Mail → MAS Token-Registierung → Synapse-Provisionierung → automatisches Standard-Onboarding via auto_join_rooms → optionaler Fein-Provisioner für zusätzliche Spaces/Räume. Für Spaces ist wichtig: Synapse behandelt Spaces “unter der Haube” als Räume, daher können Space-Aliasse in auto_join_rooms verwendet werden. Automatisch erzeugen kann Synapse über autocreate_auto_join_rooms jedoch keine Spaces; Spaces müssen also vorher existieren. citeturn16view0turn16view1turn16view5

Die Optionen lassen sich so einordnen:

Option Bewertung Stärken Schwächen Geeignet für
MAS Registration Tokens Empfohlen Nativ im MAS-Stack, sauber mit ESS/MAS, externe Tooling-Anbindung über Admin API Keine offizielle, dokumentierte “Einladungs-E-Mail-Engine” für Invite-Links; Token-Versand musst du selbst automatisieren ESS Community mit FluxCD/Ansible citeturn20view1turn12view0turn37view1turn1view2
Authentik Invitations Möglich, aber hier unnötig Fertiges Invite-/Enrollment-Modell, URL-basierte Invitations Zusätzlicher IdP-Layer, mehr bewegliche Teile, für dein Ziel kein Muss Wenn ohnehin zentraler OIDC-IdP Standard ist citeturn36search0turn36search1
Manuelle Admin-Erstellung Solide als Fallback Maximal kontrolliert, kein Self-Service nötig Kein Selbstregistrierungsfluss, hoher Betriebsaufwand Break-glass, Admin-/Testkonten citeturn27view1turn26search5

Wichtig für die Zielmodellierung: Groups/Communities sind im aktuellen Synapse-Kontext kein tragfähiges Onboarding-Ziel mehr. Die offizielle Synapse-Kommunikation beschreibt Groups/Communities als deprecated und ab Synapse 1.61 entfernt; Spaces sind der vorgesehene Ersatz. Wenn du also “Spaces, Groups, Rooms” als Zielbild denkst, sollte die reale Implementierung Spaces + Rooms heißen. citeturn17search0turn17search2

MAS-Konfiguration und GitOps-Manifeste

ESS stellt laut offizieller Doku genau den von dir benötigten Mechanismus bereit: Zusätzliche MAS-Konfiguration wird unter matrixAuthenticationService.additional in YAML-Fragmente gelegt und in die finale MAS-Konfiguration gemerged. Dasselbe gilt für Synapse unter synapse.additional und für Element Web unter elementWeb.additional. citeturn1view2turn31view1

Der kleinste sinnvolle MAS-Block für invite-basierte Self-Registration sieht so aus. Die Felder password_registration_enabled, registration_token_required und password_registration_email_required sind offiziell dokumentiert; ESS dokumentiert zusätzlich explizit, dass password_registration_email_required: false nur in Kombination mit Restriktionen wie registration_token_required: true auf öffentlich föderierenden Deployments verantwortbar ist, weil die Instanz sonst missbraucht werden kann. citeturn2view4turn20view0turn1view2

apiVersion: v1
kind: Secret
metadata:
  name: ess-mas-values-secret
  namespace: matrix
stringData:
  values.yaml: |
    matrixAuthenticationService:
      additional:
        10-account.yaml:
          config: |
            passwords:
              enabled: true
              minimum_complexity: 3

            account:
              password_registration_enabled: true
              registration_token_required: true
              password_registration_email_required: false
              password_change_allowed: true
              password_recovery_enabled: false
              login_with_email_allowed: false

            # Optional, falls du Recoveries / Verifizierung brauchst
            email:
              from: '"Matrix Auth" <no-reply@__DOMAIN__>'
              reply_to: '"Support" <support@__DOMAIN__>'
              transport: smtp
              mode: starttls
              hostname: __SMTP_HOST__
              port: 587
              username: __SMTP_USERNAME__
              password: __SMTP_PASSWORD__

            # Für halböffentliche Setups empfehlenswert
            captcha:
              service: cloudflare_turnstile
              site_key: __TURNSTILE_SITE_KEY__
              secret_key: __TURNSTILE_SECRET_KEY__

            policy:
              data:
                registration:
                  banned_usernames:
                    literals: ["admin", "root", "support"]
                emails:
                  allowed_addresses:
                    suffixes: ["@example.org"]
                requester:
                  banned_ips:
                    - 10.0.0.0/8
                    - 192.168.0.0/16

            rate_limiting:
              registration:
                burst: 2
                per_second: 0.0003
              login:
                per_ip:
                  burst: 5
                  per_second: 0.02
                per_account:
                  burst: 30
                  per_second: 0.1

Damit externe Tools Tokens anlegen können, muss die MAS Admin API aktiviert werden. Die offizielle MAS-Doku sagt ausdrücklich, dass adminapi nicht standardmäßig exponiert ist und in einen Listener aufgenommen werden muss; Zugriffe werden mit dem Scope urn:mas:admin geschützt. Für automatisierte Tools ist der dokumentierte Weg ein OAuth-Client mit client_credentials, dessen client_id in policy.data.admin_clients steht. citeturn20view1turn23view0turn23view3

apiVersion: v1
kind: Secret
metadata:
  name: ess-mas-admin-client
  namespace: matrix
stringData:
  admin-client.yaml: |
    http:
      listeners:
        - name: web
          resources:
            - name: discovery
            - name: human
            - name: oauth
            - name: compat
            - name: graphql
              playground: false
            - name: assets
              path: ./share/assets/
            - name: adminapi
          binds:
            - address: "[::]:8080"

    clients:
      - client_id: 01JV0000000000000000000001
        client_auth_method: client_secret_basic
        client_secret: "__MAS_ADMIN_CLIENT_SECRET__"

    policy:
      data:
        admin_clients:
          - 01JV0000000000000000000001

Dieses Secret bindest du wiederum als weiteren additional-Eintrag ein. So vermeidest du Dateipfad-Annahmen im Container und hältst den Client-Secret-Wert SOPS-fähig. citeturn31view1turn20view1turn23view0

apiVersion: v1
kind: Secret
metadata:
  name: ess-mas-values-secret
  namespace: matrix
stringData:
  values.yaml: |
    matrixAuthenticationService:
      additional:
        20-admin-client.yaml:
          configSecret: ess-mas-admin-client
          configSecretKey: admin-client.yaml

Für FluxCD genügt ein klassisches valuesFrom-Muster. Das bildet dein bestehendes Repo-Schema mit ConfigMaps/Secrets gut ab und bleibt mit SOPS kompatibel. citeturn31view1turn1view2

apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: matrix-stack
  namespace: matrix
spec:
  interval: 1h
  chart:
    spec:
      chart: matrix-stack
      sourceRef:
        kind: HelmRepository
        name: element-ess-oci
        namespace: flux-system
  valuesFrom:
    - kind: ConfigMap
      name: ess-synapse-custom
      valuesKey: values.yaml
    - kind: ConfigMap
      name: ess-element-custom
      valuesKey: values.yaml
    - kind: Secret
      name: ess-mas-values-secret
      valuesKey: values.yaml
  values:
    serverName: "__SERVER_NAME__"
    postgres:
      enabled: true
    synapse:
      enabled: true
      ingress:
        host: "__SYNAPSE_HOST__"
    matrixAuthenticationService:
      enabled: true
      ingress:
        host: "__MAS_HOST__"
    elementWeb:
      enabled: true
      ingress:
        host: "__ELEMENT_HOST__"
    matrixRTC:
      enabled: true
      ingress:
        host: "__MRTC_HOST__"

Für das automatische Standard-Onboarding nimmst du Synapse auto_join_rooms. Die offizielle Synapse-Doku bestätigt: neue Benutzer werden automatisch zu den gelisteten Räumen hinzugefügt; Space-Aliasse sind erlaubt, weil Spaces Räume sind; Spaces werden aber nicht automatisch erzeugt, und für private/invite-only Auto-Join-Setups muss auto_join_mxid_localpart gesetzt sein, wobei dieser Benutzer in den betreffenden Räumen Mitglied sein und Einladungsrechte haben muss. citeturn16view0turn16view1turn16view2

apiVersion: v1
kind: ConfigMap
metadata:
  name: ess-synapse-custom
  namespace: matrix
data:
  values.yaml: |
    synapse:
      additional:
        20-onboarding.yaml:
          config: |
            auto_join_rooms:
              - "#welcome:__SERVER_NAME__"
              - "#announcements:__SERVER_NAME__"
              - "#community-space:__SERVER_NAME__"

            autocreate_auto_join_rooms: false
            auto_join_mxid_localpart: system
            auto_join_rooms_for_guests: false

Für die User Experience kannst du Element Web mit einer eigenen Welcome-/Invite-Seite versehen. Offiziell unterstützt Element Web dafür embedded_pages.welcome_url, login_for_welcome, Branding-Parameter und für native OIDC-Setups OIDC-Client-Optionen. Für deinen Fall ist das vor allem nützlich, weil die offizielle MAS-Dokumentation keinen stabil dokumentierten URL-Parameter zum Prefill eines Registration Tokens beschreibt; die saubere Lösung ist daher oft eine eigene Landing-Page, die den Token erklärt oder temporär speichert und dann zum Registrierungsfluss führt. Diese Aussage ist eine Architektur-Inferenz aus den offiziellen Element- und MAS-Dokumenten. citeturn24search0turn20view1turn22view0

apiVersion: v1
kind: ConfigMap
metadata:
  name: ess-element-custom
  namespace: matrix
data:
  values.yaml: |
    elementWeb:
      additional:
        config.json: |
          {
            "embedded_pages": {
              "welcome_url": "https://__ELEMENT_HOST__/invite/index.html",
              "login_for_welcome": false
            },
            "branding": {
              "auth_header_logo_url": "https://__ELEMENT_HOST__/assets/logo.svg"
            }
          }

Ein wichtiger Betriebs-Hinweis: Es gibt aktuell ein offenes GitHub-Issue im ESS-Helm-Repository, das meldet, dass matrixAuthenticationService.additional.* in bestimmten Konstellationen ignoriert werden könne. Das ist kein offizieller Release-Hinweis, aber als Failure-Mode ernst zu nehmen. Deshalb solltest du nach jeder Änderung das gerenderte MAS-ConfigFile im Pod prüfen, bevor du mit echten Einladungen arbeitest. citeturn38view0turn31view1

Einladungsworkflow und Provisionierung

Der saubere GitOps-Ablauf ist in der Praxis fünfstufig. Zuerst legst du statische Konfiguration in Git ab: MAS-Settings, SMTP, Admin-Client, Synapse Auto-Join. Danach reconciled Flux diese Ressourcen in den Cluster. Anschließend erzeugt Ansible oder ein Shell-Skript on demand ein kurzlebiges Registration Token über die MAS Admin API. Dieses Token wird per E-Mail verschickt entweder als Link zu deiner eigenen Invite-Landing-Page oder als Nachricht mit Token + Registrierungsanweisung. Beim ersten erfolgreichen Abschluss der Registrierung provisioniert MAS den Account in Synapse; Synapse zieht dann auto_join_rooms. Wenn du danach noch gezielt Spaces/Räume abhängig von Abteilung, Rolle oder Domäne vergeben willst, übernimmt das ein Provisioner oder ein Synapse-Modul. citeturn20view1turn12view0turn16view0turn34view0

Für die MAS Admin API ist der dokumentierte Automationspfad client_credentials. Die MAS-Doku nennt dafür explizit statische Clients + policy.data.admin_clients. Das Access-Token kannst du discovery-basiert holen, damit du keine Token-Endpoint-Pfade hart codierst. citeturn20view1turn22view0turn23view0

#!/usr/bin/env bash
set -euo pipefail

MAS_ISSUER="https://account.__DOMAIN__/"
CLIENT_ID="01JV0000000000000000000001"
CLIENT_SECRET="${MAS_ADMIN_CLIENT_SECRET}"

TOKEN_ENDPOINT="$(
  curl -fsSL "${MAS_ISSUER}.well-known/openid-configuration" \
  | jq -r '.token_endpoint'
)"

ACCESS_TOKEN="$(
  curl -fsSL -u "${CLIENT_ID}:${CLIENT_SECRET}" \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    -d 'grant_type=client_credentials' \
    -d 'scope=urn:mas:admin' \
    "${TOKEN_ENDPOINT}" \
  | jq -r '.access_token'
)"

curl -fsSL -X POST "https://account.__DOMAIN__/api/admin/v1/user-registration-tokens" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d '{
    "usage_limit": 1,
    "expires_at": "2026-05-01T12:00:00Z"
  }' | jq

Die MAS-OpenAPI-Spezifikation dokumentiert für Registration Tokens genau die Felder, die du dafür brauchst: token optional, usage_limit, expires_at, dazu die Verwaltungsendpunkte zum Listen, Updaten, Revoken und Unrevoken unter /api/admin/v1/user-registration-tokens. “Valid” bedeutet laut Spec: nicht abgelaufen, nicht widerrufen und Usage-Limit nicht ausgeschöpft. Für Invite-Flows solltest du in der Regel usage_limit: 1 und kurze Expiry-Zeiten verwenden. citeturn12view0turn13view0turn37view0turn37view1turn37view2

Beispiel für Rotation bzw. Widerruf eines ungenutzten Tokens:

TOKEN_ID="01JVABCDEF..."
curl -fsSL -X POST "https://account.__DOMAIN__/api/admin/v1/user-registration-tokens/${TOKEN_ID}/revoke" \
  -H "Authorization: Bearer ${ACCESS_TOKEN}"

Wenn du Token-Limits oder die Ablaufzeit nachträglich anpassen willst, ist laut MAS-Admin-Spec PUT /api/admin/v1/user-registration-tokens/{id} der vorgesehene Weg. citeturn13view0turn11view0

In Ansible lässt sich derselbe Flow sehr gut abbilden. Hier ein minimales Beispiel mit Discovery, Token-Erzeugung und Mailversand. Der SMTP-Provider ist bewusst als Platzhalter gelassen, weil du ihn nicht spezifiziert hast. Die Speicherung des erzeugten Registration-Tokens in Git ist optional; ich würde standardmäßig nur Token-ID, Empfänger, Ablaufzeit und Versandzeitpunkt als Audit speichern, nicht das Secret selbst. citeturn20view1turn12view0turn3view1turn2view5

- name: Discover MAS token endpoint
  ansible.builtin.uri:
    url: "https://account.__DOMAIN__/.well-known/openid-configuration"
    return_content: true
  register: oidc_discovery

- name: Get MAS admin access token
  ansible.builtin.uri:
    url: "{{ oidc_discovery.json.token_endpoint }}"
    method: POST
    user: "{{ mas_admin_client_id }}"
    password: "{{ mas_admin_client_secret }}"
    force_basic_auth: true
    body_format: form-urlencoded
    body:
      grant_type: client_credentials
      scope: urn:mas:admin
    return_content: true
  register: mas_oauth

- name: Create single-use registration token
  ansible.builtin.uri:
    url: "https://account.__DOMAIN__/api/admin/v1/user-registration-tokens"
    method: POST
    headers:
      Authorization: "Bearer {{ mas_oauth.json.access_token }}"
    body_format: json
    body:
      usage_limit: 1
      expires_at: "{{ invite_expires_at }}"
    return_content: true
  register: mas_invite

- name: Build invite URL
  ansible.builtin.set_fact:
    invite_url: "https://__ELEMENT_HOST__/invite/?token={{ mas_invite.json.data.attributes.token | urlencode }}"

- name: Send invitation email
  community.general.mail:
    host: "__SMTP_HOST__"
    port: 587
    secure: starttls
    username: "__SMTP_USER__"
    password: "__SMTP_PASSWORD__"
    to: "{{ invitee_email }}"
    from: "no-reply@__DOMAIN__"
    subject: "Deine Matrix-Einladung"
    body: |
      Hallo,

      hier ist deine Einladung für Matrix.
      Einladung: {{ invite_url }}

      Das Token ist einmalig nutzbar und gültig bis {{ invite_expires_at }}.

Für die automatische Zuweisung zu Spaces/Räumen nach erster Anmeldung gibt es drei robuste Muster:

Erstens der Standardpfad über auto_join_rooms: ideal für alle neuen Benutzer, z. B. #welcome, #announcements und ein Haupt-Space. Das ist die einfachste und offizielle Lösung. Zweitens ein Post-Registration-Provisioner, der den Benutzer zusätzlich in weitere Räume einlädt oder joined. Dafür ist der offizielle Synapse-Admin-Endpunkt POST /_synapse/admin/v1/join/<room_id_or_alias> gedacht. Drittens ein Synapse-Modul, das on_user_registration oder on_user_login implementiert. Die Primärdoku beschreibt genau diese Callback-Punkte; seit Synapse 1.135 müssen Module allerdings worker-safe sein, weil on_user_registration auch auf Workern laufen kann. citeturn16view0turn27view0turn34view0turn35view0

Ein Provisioner gegen die Synapse Admin API kann so aussehen. Dabei setze ich voraus, dass du bereits einen user-bound Synapse-Admin-Token hast; dafür verweist die offizielle MAS-Doku auf den Device-Code-Flow mit Synapse-Admin-Scopes. Dasselbe Dokument zeigt auch, wie man kombinierte MAS-/Synapse-Admin-Tokens interaktiv anfordern kann. citeturn27view0turn32search1turn32search0

#!/usr/bin/env bash
set -euo pipefail

SYNAPSE_BASE="https://matrix.__DOMAIN__"
SYNAPSE_ADMIN_TOKEN="${SYNAPSE_ADMIN_TOKEN}"
USER_ID="$1"

join_room() {
  local alias="$1"
  curl -fsSL -X POST \
    -H "Authorization: Bearer ${SYNAPSE_ADMIN_TOKEN}" \
    -H 'Content-Type: application/json' \
    "${SYNAPSE_BASE}/_synapse/admin/v1/join/${alias}" \
    -d "{\"user_id\":\"${USER_ID}\"}" >/dev/null
}

join_room "#community-space:__SERVER_NAME__"
join_room "#announcements:__SERVER_NAME__"
join_room "#team-general:__SERVER_NAME__"

Wichtig ist hier die Abgrenzung zu “Claim Mappings”: Die offizielle MAS-OIDC-Doku beschreibt claims_imports für localpart, displayname, email und account_name. Daraus folgt: Claim-Mappings sind nützlich, wenn du später doch einen Upstream-OIDC-Provider ergänzen willst, aber sie lösen nicht die Mitgliedschaft in Spaces/Räumen. Für deinen Authentik-freien Invite-Flow ist das korrekte Modell also Token + Synapse-Onboarding, nicht “Claims → Rooms”. citeturn21search1turn9search4

Sicherheit und Betriebsmodell

Die sicherste Konfiguration für dein Szenario ist nicht “offene Registrierung mit etwas Deko”, sondern echte Invite-only-Registrierung. Das bedeutet konkret: password_registration_enabled: true, registration_token_required: true und nur dann password_registration_email_required: false, wenn du bewusst auf Token-Schutz, kurze Laufzeiten, Single-Use und gute Missbrauchsbarrieren setzt. Genau davor warnt die ESS-Doku ausdrücklich: Ein öffentlich föderierender Server ohne Mailpflicht und ohne starke Restriktionen wird missbraucht. citeturn2view4turn20view0turn1view2

Ich würde die Sicherheitsprofile so priorisieren:

Profil Empfohlene Settings Wann sinnvoll Kommentar
Striktes Invite-only password_registration_enabled: true, registration_token_required: true, password_registration_email_required: false, usage_limit: 1, kurze Expiry Kleine Community, kuratierte Benutzerbasis Beste Mischung aus UX und Missbrauchsschutz citeturn2view4turn12view0turn37view2
Invite-only mit Recovery Wie oben, aber password_recovery_enabled: true und SMTP voll konfiguriert Wenn Nutzer Self-Service-Passwort-Reset brauchen Erhöht Komfort, verlangt sauberen Mailbetrieb citeturn2view4turn2view5
Halböffentlich Tokenpflicht + CAPTCHA + E-Mail-Allowlist + restriktivere Rate-Limits Wenn Einladungen wiederverwendbar oder breiter verteilt werden Mehr Schutz gegen Bot-/Spam-Missbrauch citeturn3view2turn20view0turn3view1

Zu den wichtigsten Härtungsmaßnahmen gehören: Single-Use (usage_limit: 1), kurze Gültigkeit, Rate Limits für Registrierungen, optional CAPTCHA, optional Domain-Allowlist für E-Mails, Banned IP/User-Agent-Regeln und ein interner oder streng geschützter MAS-Admin-API-Zugang. MAS dokumentiert all diese Stellhebel explizit: policy.data.registration.*, emails.allowed_addresses, requester.banned_ips, CAPTCHA-Provider und rate_limiting.registration. citeturn20view0turn3view1turn3view2turn2view5

Für die Raum-/Space-Seite gilt zusätzlich: Halte sensible Onboarding-Räume invite-only und vermische “öffentlich sichtbare Discovery” nicht mit einem privaten Invite-Modell. Die Matrix-/Synapse-Dokumentation empfiehlt bei privaten Setups ausdrücklich, die Sichtbarkeit öffentlicher Räume restriktiv zu halten; Synapse hat die Standardregeln für Room-Directory-Publikation in neueren Versionen auch bewusst verschärft. citeturn17search6turn35view0

Ein subtiler, aber wichtiger Punkt ist die Trennung von MAS Admin und Synapse Admin. Offiziell ist urn:mas:admin für automatisierte client_credentials-Tools dokumentiert. Für urn:synapse:admin:* beschreibt die Scope-Referenz dagegen ein benutzergebundenes Modell; außerdem braucht Synapse-Admin zusätzlich die Matrix-API-Scopes. Daraus folgt als belastbare Betriebsregel: Reine Maschinenautomation sollte primär MAS Admin API nutzen; Synapse-Admin-Aktionen entweder über Auto-Join, über ein Synapse-Modul oder über einen echten Service-User mit benutzergebundenem Admin-Token. citeturn20view1turn32search0turn32search1

Tests, Rollout und Recovery

Der Rollout sollte nicht mit echten Einladungen beginnen, sondern mit einem vollständigen Dry-Run auf Staging oder einem Testkonto. Zuerst reconcile-st du Flux, dann prüfst du das HelmRelease und die gerenderte MAS-Konfiguration. Danach testest du die MAS Admin API, erzeugst ein kurzes Einmal-Token, registrierst einen neuen Testnutzer, überprüfst times_used/valid, kontrollierst die Raum-/Space-Mitgliedschaften und widerrufst übrige Tokens. Damit deckst du sowohl die ESS-/MAS-Seite als auch die Synapse-Onboarding-Seite einmal komplett ab. citeturn20view1turn12view0turn16view0turn27view0

Die fachliche Checkliste dafür ist kurz, aber strikt:

  • Konfiguration vorhanden: matrixAuthenticationService.additional und synapse.additional sind im gerenderten Config-Output sichtbar. Wegen des offenen ESS-Issues zu additional.* solltest du das aktiv prüfen. citeturn31view1turn38view0
  • MAS Admin API erreichbar: /api/spec.json bzw. das Anlegen eines Tokens funktioniert mit urn:mas:admin. citeturn20view1turn10view0
  • Token-Lebenszyklus korrekt: neues Token erscheint als valid, nach Nutzung steigt times_used, bei usage_limit: 1 ist es anschließend nicht mehr gültig. citeturn12view0turn37view2
  • Onboarding korrekt: Testnutzer landet im Haupt-Space, in Welcome/Announcements und falls aktiviert in weiteren Zielräumen. citeturn16view0turn27view0
  • Missbrauchsschutz greift: Ablaufzeit, CAPTCHA, Rate-Limits und Policy-Regeln sind aktiv. citeturn3view1turn3view2turn20view0

Die wichtigsten Failure Modes und ihre Recovery-Schritte sind diese:

Symptom Wahrscheinliche Ursache Recovery
Registrierung fordert trotzdem E-Mail-Verifikation password_registration_email_required ist noch true oder falsches MAS-Configfile aktiv Gerendertes MAS-Configfile prüfen, Werte korrigieren, neu deployen citeturn2view4turn1view2turn38view0
403 bei Token-Erzeugung OAuth-Client nicht in policy.data.admin_clients oder falscher Scope Client-/Policy-Fragment korrigieren, neuen Access Token holen citeturn20view1turn23view3
404 auf /api/admin/v1/... adminapi nicht in http.listeners.resources aktiviert Listener-Block korrigieren und Redeploy citeturn20view1
User landet nicht im Space Space existiert nicht, Space wurde nicht separat angelegt, oder auto_join_mxid_localpart-User kann nicht einladen Space vorab anlegen, system-User joinen lassen, Rechte prüfen; Spaces werden nicht automatisch erstellt citeturn16view0turn16view1turn16view2
Feingranulare Raumzuweisung klappt nicht Du versuchst Membership über Claim-Mapping zu lösen Membership über Auto-Join, Provisioner oder Synapse-Modul abbilden citeturn21search1turn34view0
“Groups” lassen sich nicht mehr sinnvoll zuweisen Groups/Communities sind im aktuellen Synapse-Modell entfernt Auf Spaces + Rooms umstellen citeturn17search0turn17search2

Wenn ein Invite-Token versehentlich verschickt wurde, ist der saubere Recovery-Schritt nicht das HelmRelease zu verändern, sondern das Token per MAS Admin API zu revoken und bei Bedarf ein neues zu erzeugen. Wenn ein Benutzer zwar registriert wurde, aber Raumzuweisungen fehlen, ist die schnellste operative Korrektur ein nachgelagerter Join über den Synapse-Admin-Endpunkt. Wenn ein Synapse-Modul dafür verantwortlich ist, beachte die Worker-Eigenschaft von on_user_registration. citeturn13view0turn27view0turn35view0

Quellenbasis und offene Punkte

Die belastbarsten Primärquellen für dieses Design sind die offiziellen Dokumentationen zu ESS, MAS, Synapse, Element Web und LiveKit. Für den Vergleichspfad “Authentik Invitations” ist die offizielle Authentik-Doku hinzugezogen worden.

Die Priorisierung war dabei:

  • ESS Advanced-Doku zur Einbindung zusätzlicher Konfigurationsdateien für MAS, Synapse, Element Web und MatrixRTC. citeturn1view2
  • ESS Chart-Values zur Bestätigung, dass matrixAuthenticationService.additional/synapse.additional/elementWeb.additional offizielle Wertepfade sind. citeturn31view1turn31view4
  • MAS-Konfigurationsreferenz für account.*, captcha, policy, rate_limiting, email, clients, admin_clients und HTTP-Listener. citeturn2view4turn20view0turn22view0turn23view0
  • MAS Admin API und OpenAPI-Schema für Token-Erzeugung, Update, Revoke, Validitätsbegriff und Automationsmodell. citeturn20view1turn12view0turn13view0turn37view1turn37view2
  • MAS Scope- und Access-Token-Doku für den Unterschied zwischen urn:mas:admin und benutzergebundenem Synapse-Admin-Zugriff. citeturn32search0turn32search1
  • Synapse-Konfigurationsmanual für auto_join_rooms, Space-Aliasse, autocreate_auto_join_rooms und auto_join_mxid_localpart. citeturn16view0turn16view1turn16view2
  • Synapse-Admin- und Modul-Doku für Raumzuweisung per API und Callback-Punkte on_user_registration / on_user_login. citeturn27view0turn34view0turn35view0
  • Element-Web-Konfiguration für Welcome-/Branding-/OIDC-nahe UX-Optionen. citeturn24search0
  • LiveKit-/Element-Call-Doku als optionale, vom Registrierungsfluss getrennte RTC-Schicht. citeturn25search0turn25search13
  • Authentik-Invitationsdoku nur für den Vergleich innerhalb der Optionen-Tabelle. citeturn36search0turn36search1

Offen beziehungsweise bewusst konservativ behandelt bleiben drei Punkte. Erstens beschreibt die offizielle MAS-Dokumentation nach dem hier ausgewerteten Stand keinen stabil dokumentierten Invite-Link-Parameter, mit dem ein Registration Token sicher per URL in die MAS-UI vorbefüllt wird; deshalb empfehle ich eine eigene Landing-Page oder Token-Übermittlung im Mailtext. Zweitens ist client_credentials sauber für die MAS Admin API dokumentiert, nicht aber als Standardmuster für Synapse-Admin-Automation; für Synapse-Onboarding ist deshalb auto_join_rooms oder ein user-/modulbasierter Pfad robuster. Drittens gibt es aktuell ein offenes ESS-Helm-Issue zu matrixAuthenticationService.additional.*; deshalb solltest du das gerenderte MAS-Configfile im Deployment immer verifizieren, bevor du produktive Einladungen versendest. citeturn20view1turn32search0turn38view0