feat: POST /service-token — Service-Token für weckruf-<org> erzeugen/rotieren #1

Closed
opened 2026-06-23 17:36:12 +02:00 by tobias · 1 comment
Owner

Kontext

Der Service-Token (Credential C, womit WeckRuf gegen ntfy publisht) ist
aktuell das einzige Credential im ntfy-Setup, das komplett manuell gehalten
wird: Service-User weckruf-<org> wird von Hand via ntfy token add erzeugt,
der Wert per Klartext-.txt ausgelesen und in die WeckRuf-Config kopiert.
Operator-Passwörter (Credential B) sind dagegen vollautomatisch über
/set-password. Diese Asymmetrie ist die Wurzel der .txt-Krücke.

Vorschlag: POST /service-token

Neuer Endpoint analog zu /set-password, Bearer-Auth (org-scoped wie /sync):

POST /service-token   { "org": "uhv21" }
→ 200 { "org":"uhv21", "service_user":"weckruf-uhv21", "token":"tk_…" }   (einmalig)

Logik:

  1. user := cfg.ServiceUser(org)weckruf-<org>existiert bereits
    (internal/config/config.go:50-52)
  2. AddUser(user) idempotent — existiert (internal/ntfy/ntfy.go)
  3. SetAccess(user, org+"-*", "write") — existiert (internal/ntfy/ntfy.go)
  4. ROTATION: alle bestehenden Tokens von user revoken, dann genau einen
    neuen erzeugen → immer exakt 1 aktiver Token
  5. neuen Token einmalig in der Response zurückgeben (wie das PW bei /set-password)

Warum Rotation statt Append (wichtig)

ntfy überschreibt Tokens nie — jedes ntfy token add legt eine neue Zeile an.
Ein naiver „Token erzeugen"-Button würde bei jedem Klick einen weiteren
weckruf-<org>-Token anhäufen — alle bleiben gültig (Sicherheits- + Hygiene-
Problem, kein Überblick, welcher in welcher .env steckt). Real beobachtet: der
Admin-User tobias hat durch wiederholtes Erzeugen bereits 4 Tokens. Daher:
revoke-all-then-create-one.

Neue CLI-Wrapper nötig

  • AddToken(user) (string, error)ntfy token add <user>, Token aus stdout parsen
  • ListTokens(user) + RemoveToken(user, id)ntfy token remove <user> <id>
    (für die Rotation)

Sicherheit

  • Token-Wert nur in der HTTP-Response (einmalig), nie geloggt — wie /set-password.
  • Der „never touched"-Schutz im Reconcile (PlanReconcile) bleibt: dieser Endpoint
    ist ein separater, expliziter Aufruf, kein Teil von /sync.

Schwester-Issues (Stufe 2 / Voll-Automatik, Cross-Repo)

  • TEC/datastar-app — „Service-Token erzeugen/rotieren"-Button + Auto-Injektion in WeckRuf/config
  • TEC/weckruf — Service-Token aus dynamischer WeckRuf/config statt static config.yaml lesen
## Kontext Der **Service-Token** (Credential C, womit WeckRuf gegen ntfy publisht) ist aktuell das einzige Credential im ntfy-Setup, das komplett **manuell** gehalten wird: Service-User `weckruf-<org>` wird von Hand via `ntfy token add` erzeugt, der Wert per Klartext-`.txt` ausgelesen und in die WeckRuf-Config kopiert. Operator-Passwörter (Credential B) sind dagegen vollautomatisch über `/set-password`. Diese Asymmetrie ist die Wurzel der `.txt`-Krücke. ## Vorschlag: `POST /service-token` Neuer Endpoint analog zu `/set-password`, Bearer-Auth (org-scoped wie `/sync`): ``` POST /service-token { "org": "uhv21" } → 200 { "org":"uhv21", "service_user":"weckruf-uhv21", "token":"tk_…" } (einmalig) ``` Logik: 1. `user := cfg.ServiceUser(org)` → `weckruf-<org>` — **existiert bereits** (`internal/config/config.go:50-52`) 2. `AddUser(user)` idempotent — existiert (`internal/ntfy/ntfy.go`) 3. `SetAccess(user, org+"-*", "write")` — existiert (`internal/ntfy/ntfy.go`) 4. **ROTATION:** alle bestehenden Tokens von `user` revoken, dann genau **einen** neuen erzeugen → immer exakt 1 aktiver Token 5. neuen Token einmalig in der Response zurückgeben (wie das PW bei `/set-password`) ## Warum Rotation statt Append (wichtig) ntfy überschreibt Tokens nie — jedes `ntfy token add` legt eine **neue** Zeile an. Ein naiver „Token erzeugen"-Button würde bei jedem Klick einen weiteren `weckruf-<org>`-Token anhäufen — alle bleiben gültig (Sicherheits- + Hygiene- Problem, kein Überblick, welcher in welcher `.env` steckt). Real beobachtet: der Admin-User `tobias` hat durch wiederholtes Erzeugen bereits **4 Tokens**. Daher: **revoke-all-then-create-one**. ## Neue CLI-Wrapper nötig - `AddToken(user) (string, error)` → `ntfy token add <user>`, Token aus stdout parsen - `ListTokens(user)` + `RemoveToken(user, id)` → `ntfy token remove <user> <id>` (für die Rotation) ## Sicherheit - Token-Wert nur in der HTTP-Response (einmalig), **nie geloggt** — wie `/set-password`. - Der „never touched"-Schutz im Reconcile (`PlanReconcile`) bleibt: dieser Endpoint ist ein separater, expliziter Aufruf, kein Teil von `/sync`. ## Schwester-Issues (Stufe 2 / Voll-Automatik, Cross-Repo) - **TEC/datastar-app** — „Service-Token erzeugen/rotieren"-Button + Auto-Injektion in `WeckRuf/config` - **TEC/weckruf** — Service-Token aus dynamischer `WeckRuf/config` statt static `config.yaml` lesen
Author
Owner

Umgesetzt in v0.2.0 (Commit f3a5f96).

Eine bewusste Abweichung von der Issue-Spec: statt revoke-all-then-create-one wurde create-one-then-revoke-old implementiert — frischen Token zuerst prägen, dann die alten widerrufen. Selbes Endergebnis (genau 1 aktiver Token, kein Anhäufen), aber kein Zeitfenster ohne gültigen Token → WeckRuf publisht durchgehend weiter. Per Test abgesichert, dass der frisch geprägte Token nie widerrufen wird.

Release: https://git.te-c.net/TEC/ntfy-user-provisioner/releases/tag/v0.2.0

Umgesetzt in v0.2.0 (Commit f3a5f96). Eine bewusste Abweichung von der Issue-Spec: statt *revoke-all-then-create-one* wurde **create-one-then-revoke-old** implementiert — frischen Token zuerst prägen, dann die alten widerrufen. Selbes Endergebnis (genau 1 aktiver Token, kein Anhäufen), aber **kein Zeitfenster ohne gültigen Token** → WeckRuf publisht durchgehend weiter. Per Test abgesichert, dass der frisch geprägte Token nie widerrufen wird. Release: https://git.te-c.net/TEC/ntfy-user-provisioner/releases/tag/v0.2.0
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TEC/ntfy-user-provisioner#1
No description provided.