feat: add the concierge bot for expiring guest invitations
Turns guest onboarding from an admin-only click in the Authentik UI into a traceable command a defined circle can run: !einladen creates a single-use invitation valid for three days, !verlaengern extends it twice at most, !freischalten makes it permanent, and expired accounts are deactivated automatically. Authorisation is deliberately twofold - the Authentik group decides, the invite room makes it visible. A group alone leaves no trace of who invited whom; a room alone would authorise anyone who gets in. Two deployment details matter: exactly one replica with Recreate, because a second instance would execute every command twice; and the script ConfigMap keeps its name hash so a change actually restarts the pod, avoiding the trap described in #50. Endpoints and field names were taken from the running Authentik OpenAPI schema, not guessed. Refs axion1337.chat/axion1337.chat-gitops#48
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
# @concierge — Gäste-Einladungen mit Ablauf
|
||||
|
||||
**Status**: gebaut, wartet auf Zugangsdaten · Issue [#48](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/48)
|
||||
|
||||
Ein kleiner Bot, der Einladungslinks erzeugt, Gastkonten nach drei Tagen ablaufen
|
||||
lässt und die dauerhafte Freischaltung an eine bewusste Admin-Handlung bindet.
|
||||
|
||||
## Warum es diesen Bot gibt
|
||||
|
||||
Registrierung läuft in diesem Stack **ausschließlich über Authentik**. Bis jetzt
|
||||
hieß das: Wer jemanden einladen will, klickt in der Authentik-Oberfläche einen
|
||||
Invitation-Token zusammen. Das können nur Admins, es hinterlässt keine Spur, wer
|
||||
wen eingeladen hat, und ein Gastkonto bleibt für immer bestehen.
|
||||
|
||||
Der Bot macht daraus einen Vorgang, den ein festgelegter Kreis selbst auslösen
|
||||
kann — nachvollziehbar und mit eingebautem Ablauf.
|
||||
|
||||
## Wie es funktioniert
|
||||
|
||||
```
|
||||
!einladen <name> → Authentik-Invitation (einmalig, 3 Tage) + Link im Raum
|
||||
Gast registriert sich → Konto trägt threadnet_guest_expires_at
|
||||
!verlaengern @gast → +1 Tag, höchstens 2×
|
||||
!freischalten @gast → Ablauf entfernen, in members-Gruppe (nur Admins)
|
||||
(nichts davon) → Bot deaktiviert das Konto nach Ablauf
|
||||
```
|
||||
|
||||
### Berechtigung ist zweiteilig — und das ist Absicht
|
||||
|
||||
**Authentik-Gruppe UND Einladungsraum.** Die Gruppe entscheidet, der Raum macht
|
||||
sichtbar. Eine Gruppe allein ist unsichtbar: Niemand sähe, wer eingeladen hat.
|
||||
Ein Raum allein autorisiert nicht: Wer hineinkommt, dürfte alles. Zusammen ergibt
|
||||
sich beides, und jede Einladung hinterlässt einen nachlesbaren Eintrag.
|
||||
|
||||
### Zwei Dinge, die beim Umbauen leicht kaputtgehen
|
||||
|
||||
⚠️ **Genau eine Instanz.** `replicas: 1` **und** `strategy: Recreate`. Der Bot
|
||||
hält eine `/sync`-Schleife; zwei Instanzen führen jedes Kommando doppelt aus. Bei
|
||||
`RollingUpdate` liefen während eines Deploys kurzzeitig zwei.
|
||||
|
||||
⚠️ **Die ConfigMap trägt bewusst einen Namens-Hash.** Anders als beim
|
||||
ClamAV-Modul steht hier **kein** `disableNameSuffixHash: true`. Dadurch ändert
|
||||
sich der ConfigMap-Name mit dem Skript, kustomize zieht die Referenz nach, und
|
||||
der Pod startet von selbst neu. Ohne das hätten wir den Fall aus gitops#50:
|
||||
geänderte Datei im Repo, alter Stand im laufenden Prozess.
|
||||
|
||||
### Fehlerverhalten ist absichtlich unsymmetrisch
|
||||
|
||||
- **Einladen und Freischalten scheitern laut.** Lieber keine Einladung als eine,
|
||||
von der niemand weiß.
|
||||
- **Die Ablaufprüfung deaktiviert nur, wenn Authentik sauber geantwortet hat.**
|
||||
Ein API-Fehler darf nicht dazu führen, dass Konten reihenweise abgeschaltet
|
||||
werden; im Zweifel bleibt ein Gast einen Durchlauf länger aktiv.
|
||||
|
||||
## Was zur Inbetriebnahme fehlt
|
||||
|
||||
Der Bot ist ausgerollt, **startet aber nicht**, solange das Secret fehlt — der Pod
|
||||
meldet `secret "concierge-credentials" not found`. Das ist gewollt sichtbar; ein
|
||||
Bot, der still nichts tut, wäre schlechter.
|
||||
|
||||
### 1. Matrix-Konto anlegen
|
||||
|
||||
```bash
|
||||
kubectl exec -it -n matrix deploy/matrix-stack-matrix-authentication-service -- \
|
||||
mas-cli manage register-user concierge --yes
|
||||
kubectl exec -it -n matrix deploy/matrix-stack-matrix-authentication-service -- \
|
||||
mas-cli manage issue-compatibility-token concierge
|
||||
```
|
||||
|
||||
### 2. Authentik-Token
|
||||
|
||||
*Admin → Verzeichnis → Tokens*. Braucht Schreibrechte auf Nutzer, Gruppen und
|
||||
Invitations. Ein eigenes Dienstkonto ist sauberer als ein Admin-Token.
|
||||
|
||||
### 3. Einladungsraum
|
||||
|
||||
Invite-only anlegen, `@concierge` einladen. Die Raum-ID ist Teil des Secrets, weil
|
||||
sie zusammen mit den Token gepflegt wird und sich beim Neuanlegen ändert.
|
||||
|
||||
### 4. Gruppen in Authentik
|
||||
|
||||
`invite-berechtigt` (wer einladen darf) und `members` (wohin Freigeschaltete
|
||||
kommen). Namen sind über `INVITE_GROUP` / `MEMBER_GROUP` änderbar.
|
||||
|
||||
### 5. Secret
|
||||
|
||||
```bash
|
||||
kubectl create secret generic concierge-credentials -n matrix \
|
||||
--from-literal=matrix-token='…' \
|
||||
--from-literal=authentik-token='…' \
|
||||
--from-literal=matrix-room-id='!….:axion1337.chat' \
|
||||
--dry-run=client -o yaml > /tmp/s.yaml
|
||||
sops -e /tmp/s.yaml > apps/production/concierge-secret.yaml
|
||||
```
|
||||
|
||||
⚠️ Über SOPS ins Repo, nicht mit `kubectl apply` von Hand — sonst kennt Flux das
|
||||
Secret nicht und es fehlt nach einem Wiederaufbau des Clusters.
|
||||
|
||||
## Grenzen
|
||||
|
||||
- **Matrix-Localpart = Authentik-Benutzername.** Gilt hier, weil MAS aus
|
||||
Authentik provisioniert. Stimmt es einmal nicht, findet der Bot den Nutzer
|
||||
nicht und sagt das — er rät nicht.
|
||||
- **Ein deaktiviertes Konto ist nicht gelöscht.** Räume und Nachrichten bleiben.
|
||||
Löschen ist bewusst nicht Sache des Bots.
|
||||
- **Draupnir-Verzahnung** (Gast-Label → eingeschränkte Räume) ist Stufe 2 und
|
||||
nicht Teil dieser Fassung.
|
||||
Reference in New Issue
Block a user