From d5c5862654b9ebd362dcd55dd5baf7619772a1b3 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 27 Oct 2026 13:52:17 +0100 Subject: [PATCH] keep the matrix appservice registration in bao, with the host file as the last copy --- docs/integrations/matrix.md | 41 +- docs/swarm/credentials-reference.md | 68 +- nix/host-modules/hive-matrix.nix | 50 +- nix/host-modules/swarm-bao.nix | 35 +- nix/module-eval/bao-grants.nix | 27 +- nix/module-eval/bao-matrix-reader.nix | 63 +- .../src/matrix_account/agent_token.rs | 2 +- swarm-matrix-ctl/README.md | 15 +- swarm-matrix-ctl/src/appservice.rs | 790 ++++++++++++++++-- swarm-matrix-ctl/src/main.rs | 22 +- swarm-matrix-ctl/src/registration.rs | 102 --- swarm-secret-client/src/matrix.rs | 68 +- 12 files changed, 1012 insertions(+), 271 deletions(-) delete mode 100644 swarm-matrix-ctl/src/registration.rs diff --git a/docs/integrations/matrix.md b/docs/integrations/matrix.md index 7fb3a558..2f60f311 100644 --- a/docs/integrations/matrix.md +++ b/docs/integrations/matrix.md @@ -129,10 +129,11 @@ creation doesn't depend on registration being open to anyone who learns a token. 1. **`swarm-matrix-ctl appservice render`**, inside the `hive-matrix` - container and before tuwunel starts, mints the registration's - `as_token` and its spec-required `hs_token` when they're missing, and - renders the registration to - `/var/lib/swarm-matrix-appservice/swarm.yaml` (mode `0600 root:root`). + container and before tuwunel starts, renders the registration to + `/var/lib/swarm-matrix-appservice/swarm.yaml` (mode `0600 root:root`) + from the `as_token` and spec-required `hs_token` beside it. It mints + them only on a container that has none, and never reaches the store, so + a store outage can't keep the homeserver down. 2. **systemd `LoadCredential=`** copies the registration into `/run/credentials/tuwunel.service/swarm-appservice.yaml`, owned by tuwunel's dynamic user with mode `0400`, at service start. The source @@ -168,13 +169,21 @@ namespace non-exclusive. It's the swarm's identity on the homeserver: `swarm-controller` creates every agent's account and every hive's sender account with it. -`swarm-matrix-ctl appservice render` mints its tokens inside the -`hive-matrix` container, before tuwunel starts, and renders the -registration tuwunel loads as a credential. `appservice publish` -then writes its `as_token` to -`swarm/controller/swarm-controller/matrix/appservice-token`. No hive's -policy reaches that path: only matrix-ctl (which writes it) and -`swarm-controller` (which reads it) hold a grant to it. +The swarm secret store holds the registration's tokens, at +`swarm/controller/swarm-controller/matrix/appservice-registration`. +`swarm-matrix-ctl appservice render` renders the registration tuwunel +loads as a credential from the copy of them in the `hive-matrix` +container, before tuwunel starts and without the store. +`appservice sync` runs beside tuwunel on a timer. The store's tokens win: +when they differ from the container's, it writes them there. It +restarts tuwunel whenever the registration is newer than tuwunel's last +start, so the next run retries one that failed after writing. A store that holds none takes the container's +tokens with a create-only write, so a running swarm keeps its value, and +a container that has none mints a pair. `sync` then writes the `as_token` +to `swarm/controller/swarm-controller/matrix/appservice-token`. No hive's +policy reaches either path: only matrix-ctl (which holds `create` and +`read` on the registration, and writes the token) and `swarm-controller` +(which reads only the token) hold a grant. `@swarm` is the **one** account the homeserver promotes to admin at boot (`admin_execute`). Only those two principals read its token, which is @@ -277,9 +286,9 @@ required.
Appservice identity -`swarm-matrix-ctl appservice render` mints the appservice tokens and -renders the registration before tuwunel starts, so every boot has both -halves. +`swarm-matrix-ctl appservice render` renders the registration from the +container's copy of the appservice tokens before tuwunel starts, so every +boot has both halves, with the store up or down. - **Access tokens live on the device that minted them.** @@ -289,8 +298,8 @@ halves. (above); a hive's sender account is never promoted. - **The value that matters lives at `swarm/controller/swarm-controller/matrix/appservice-token`.** - `appservice publish` writes the container's copy there whenever the - store's differs, and `swarm-controller` reads it from there. + `appservice sync` writes it there whenever the store's differs from the + registration's `as_token`, and `swarm-controller` reads it from there.
diff --git a/docs/swarm/credentials-reference.md b/docs/swarm/credentials-reference.md index 2a5a31af..f0f75003 100644 --- a/docs/swarm/credentials-reference.md +++ b/docs/swarm/credentials-reference.md @@ -14,39 +14,40 @@ the rest of the cell says how. -| store path | minter | reader | automatic re-mint | automatic re-pull | -| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `swarm/agents//matrix/main` | `swarm-controller`, at agent creation and every 5 minutes | the agent | ✅ when missing or rejected by the homeserver | ✅ the daemon restarts within 5 minutes of a rejection | -| `swarm/agents//matrix/` | `swarm-controller`, from a credential you hand it | the agent | must be stated | ✅ retried within 2 to 16 minutes after a rejection | -| `swarm/agents//matrix-recovery/` | `swarm-controller`, a random passphrase per account | the agent | ❌ never replaced: the account's key backup opens only with this passphrase | ❌ read when the account comes up | -| `swarm/controller/swarm-controller/matrix/appservice-token` | `swarm-matrix-ctl` in `hive-matrix`, once | `swarm-controller` | ❌ minted once | ✅ read every 5 minutes | -| `swarm/controller/swarm-controller/oidc/client` | authelia, first boot | `swarm-controller` | ❌ minted once; `swarm-secret-publish` republishes a re-mint | ✅ re-read when authelia refuses it | -| `swarm/controller/swarm-controller/forge/token` | the forge, once | `swarm-controller` | ❌ minted once; delete `/var/lib/forgejo/swarm-controller-token-name` in the forge container to mint a new one | ✅ re-read on a 401 | -| `swarm/controller/swarm-controller/grafana/token` | `swarm-bao-grafana-controller-token` on Grafana's host | `swarm-controller` | ✅ every 10 minutes, when Grafana refuses it or none is stored | ✅ re-read on a 401 | -| `swarm/controller/swarm-controller/forge/webhook-secret` | `swarm-controller`, at start, from its old `webhook-secret` file; a new one only when there is no file | `swarm-controller` | ❌ minted once; a rotated secret fails every delivery until the forge hooks are re-registered | ❌ read at start | -| `swarm/agents//bao-mtls` | the store's agent PKI mount (`deploy.bao.agentPkiMountPath`), at agent creation | `hive-c0re`, then the agent | ✅ re-issued every 5 minutes once past half its 7-day validity | ✅ the agent re-reads every 10 minutes; it renews only while its current leaf is valid | -| `swarm/agents//queue` | `swarm-controller`, at agent creation | the agent; the queue, log and metrics auth services | ✅ re-minted at 45 days; skipped for `Destroyed` agents, whose path is deleted | ✅ read on every reconnect; an open connection keeps the old secret for up to an hour; the log and metrics stores honour a replaced secret for up to `deploy.victorialogs.authVerdictTtl` and `deploy.victoriametrics.agentAuthVerdictTtl` (60 s by default) | -| `swarm/agents//forge-token` | `swarm-controller`, at agent creation and every 5 minutes | the agent | ✅ when missing or no longer matching the forge | ✅ every 10 minutes | -| `swarm/agents//forge/