Account minting, knowledge sync, the archetype refresh, the matrix appservice flow and the SSO secret delivery move to pages under 4-implementation/swarm. Each handbook page keeps what an operator does or sees, with a pointer.
193 lines
10 KiB
Markdown
193 lines
10 KiB
Markdown
# Matrix homeserver: account provisioning
|
|
|
|
How the swarm's appservice creates accounts on the homeserver, where its tokens
|
|
live, and the settings the homeserver starts with. The operator's view is
|
|
[the matrix page](../../2-handbook/swarm/matrix.md).
|
|
|
|
## Provisioning flow (appservice)
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
|
|
Registration is closed. **The swarm's appservice** creates accounts: agents
|
|
never see an appservice token, and an agent only ever receives its own
|
|
`access_token`.
|
|
|
|
<!-- vale write-good.Passive = YES -->
|
|
|
|
The appservice has no URL (`url: null` in its registration), so the
|
|
homeserver never calls out to it and there is no service to run. What the
|
|
registration buys is an identity the homeserver recognises — which is why
|
|
no secret has to be equal on both sides of the wire, and why account
|
|
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, fetches the `as_token` and
|
|
spec-required `hs_token` from the store and renders the registration to
|
|
`/run/swarm-matrix-appservice/swarm.yaml` (mode `0600 root:root`) on the
|
|
container's tmpfs, so no token sits on disk. It mints a pair only when
|
|
the store answers and holds none. While the store doesn't answer, `render`
|
|
fails and retries, and tuwunel stays 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
|
|
file stays `root:root 0600` — no `chown :tuwunel` / `chmod 0640` /
|
|
GID-pin gymnastics required. Keeps `DynamicUser = true` +
|
|
`PrivateUsers = true` intact.
|
|
3. tuwunel's `appservice_dir` points at the credentials directory. It
|
|
reads only `.yaml`/`.yml` entries from there, so the sibling
|
|
credentials are invisible to it. The `.yaml` suffix on the credential
|
|
id is what makes this work.
|
|
4. **hive-c0re** holds no appservice token, creates no account and does
|
|
not talk to matrix.
|
|
5. **`swarm-controller` creates every account**: each agent's, with its
|
|
token stored at `swarm/agents/<agent>/matrix/main` for the agent's
|
|
`hive-matrix-daemon` to read as the agent itself, and each hive's
|
|
sender account (below).
|
|
|
|
A homeserver whose `swarm-matrix-ctl` has no store identity
|
|
(`services.hyperhive.deploy.matrix.ctlBaoClientCertFile`) loads no
|
|
registration, and nothing creates accounts on it.
|
|
|
|
On the store's host `ctlBaoClientCertFile` / `ctlBaoClientKeyFile` default
|
|
to the leaf `glue-bao-tls.nix` mints there. A homeserver host without the
|
|
store gets no default, even when it holds the hive's own reader
|
|
`deploy.bao.clientCertFile`: issue a leaf whose subject is
|
|
`deploy.bao.matrixCtlCommonName`, place it, and name it in both options.
|
|
Until you do, `swarm-bao-matrix-oidc` fails naming both.
|
|
|
|
### The swarm's appservice, and its admin sender
|
|
|
|
The registration's id is `swarm`, its sender `@swarm`, and its user
|
|
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.
|
|
|
|
The swarm secret store holds the registration's tokens, at
|
|
`swarm/controller/swarm-controller/matrix/appservice-registration`.
|
|
`swarm-matrix-ctl appservice render` fetches them and renders the
|
|
registration tuwunel loads as a credential onto the `hive-matrix`
|
|
container's tmpfs, before tuwunel starts.
|
|
A render that can't reach the store fails and retries, and tuwunel, which fails to start without the registration, retries until one has landed.
|
|
`appservice sync` runs beside tuwunel on a timer. The store's tokens win:
|
|
when they differ from the rendered copy, it rewrites it. It
|
|
restarts tuwunel whenever the registration is newer than tuwunel's last
|
|
start, so the next run retries one that failed after writing, and starts
|
|
one that failed because `render` couldn't reach the store. A store that
|
|
holds none takes the tokens an earlier version kept in
|
|
`/var/lib/swarm-matrix-appservice` with a create-only write, so a running
|
|
swarm keeps its value; the files stay, unused. A host with neither files nor stored tokens 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
|
|
the difference from the hive sender below: every hive reads its own
|
|
sender's token. `swarm` is a reserved name, so nothing can create an agent
|
|
as this account.
|
|
|
|
`swarm-controller` mints each agent's account with the device id
|
|
`hyperhive-<agent>`, and a login on that device replaces its token. The controller therefore reads
|
|
the stored token back with `whoami` every five minutes and re-mints only
|
|
when it's missing, unknown to the homeserver, or someone else's. An
|
|
agent's daemon picks a re-minted token up on its next start, and a timer
|
|
restarts it while it's down.
|
|
|
|
### A hive's sender account, and why it isn't an admin
|
|
|
|
`@hive-<hive>:<server_name>` is the account a hive acts as.
|
|
`swarm-controller` creates it with the swarm's appservice token, for every
|
|
hive in its directory. It's an **ordinary account**: nothing promotes it;
|
|
the homeserver's `admin_execute` promotes only `@swarm`.
|
|
|
|
**One account per hive.** The localpart carries the hive's name, so a swarm
|
|
whose hives share a homeserver gives each of them its own identity: the
|
|
homeserver attributes an action to the hive that took it, and revoking one
|
|
hive's standing leaves the others alone.
|
|
|
|
Its access token is the **sender token**. `swarm-controller`, its only
|
|
minter, mints it for every hive with the swarm's appservice token and
|
|
publishes it to `swarm/hives/<hive>/matrix/sender-token`, inside the hive's own
|
|
read grant (`swarm/hives/<hive>/*`). Nothing on the hive uses it any more:
|
|
hive-c0re used to present it for its Space and invite calls and no longer
|
|
makes any. The name says what it authenticates as — an account the
|
|
swarm's appservice creates — rather than any privilege level, because it
|
|
carries none.
|
|
|
|
Promoting a user to homeserver admin and resetting a password both need an
|
|
admin **sender**: `!admin …` messages into `#admins:<server_name>`, and
|
|
tuwunel only treats a message as a command when its sender is already an
|
|
admin. Of the swarm's own accounts only `@swarm` is a homeserver admin
|
|
(operators are too, see
|
|
[Operators are homeserver admins](../../2-handbook/swarm/matrix.md#operators-are-homeserver-admins)), so
|
|
both are swarm-level operations.
|
|
|
|
<details><summary>Appservice identity</summary>
|
|
|
|
`swarm-matrix-ctl appservice render` renders the registration from the
|
|
store's copy of the appservice tokens before tuwunel starts, so every
|
|
boot has both halves. With the store down or sealed, tuwunel waits for it.
|
|
|
|
<!-- vale write-good.Passive = NO -->
|
|
|
|
- **Access tokens live on the device that minted them.**
|
|
`login_with_password` stays on, so the password fallback works
|
|
too.
|
|
- **The homeserver's `admin_execute` promotes only `@swarm` at boot**
|
|
(above); a hive's sender account is never promoted.
|
|
- **The value that matters lives at
|
|
`swarm/controller/swarm-controller/matrix/appservice-token`.**
|
|
`appservice sync` writes it there whenever the store's differs from the
|
|
registration's `as_token`, and `swarm-controller` reads it from there.
|
|
<!-- vale write-good.Passive = YES -->
|
|
|
|
</details>
|
|
|
|
## Rollout settings
|
|
|
|
- `allow_federation = true` at the protocol level so swarms can be
|
|
wired up later by extending `trustedServers` without a homeserver
|
|
restart. `trusted_servers = []` keeps it effectively closed
|
|
until you list peers.
|
|
- `allow_registration = false`. tuwunel checks this flag only for
|
|
requests that arrive **without** an appservice token, so the swarm's
|
|
appservice still creates accounts and tuwunel refuses everyone else. With no
|
|
registration token configured, `allow_registration = true` makes
|
|
tuwunel refuse to start unless
|
|
`yes_i_am_very_very_sure_…_open_registration_…` is also set.
|
|
- `allow_encryption` — server-side E2EE switch, sourced from
|
|
`services.hyperhive.deploy.matrix.allowEncryption` (**default `false`**, opt-in).
|
|
Off by default because on the swarm's own homeserver the operator
|
|
already controls the transport; turn it on for encrypted rooms on
|
|
external / federated homeservers or to keep contents opaque to the
|
|
homeserver admin. **The agent matrix client always supports decryption
|
|
regardless of this flag** — it uses the `e2e-encryption` feature of
|
|
`matrix-sdk` so it can read encrypted rooms it's invited to even when
|
|
this homeserver doesn't permit room encryption. matrix-sdk stores
|
|
crypto keys in the per-agent sqlite store under the state dir; they persist across
|
|
restarts. `read_room` decrypts via
|
|
`room.messages()` — UTD events surface as `event_type =
|
|
"m.room.encrypted"` with `body = "[unable to decrypt]"`.
|
|
The daemon bootstraps cross-signing on the bearer token alone, which
|
|
works where the homeserver skips re-auth for a first key upload
|
|
(MSC3967, as tuwunel does).
|
|
- **Key backup.** `swarm-controller` mints a random recovery passphrase
|
|
for every matrix account an agent has, at
|
|
`swarm/agents/<agent>/matrix-recovery/<account>` in the swarm secret
|
|
store, and never replaces it. When `hive-matrix-daemon` brings an
|
|
account up it reads that passphrase: with no backup on the account's
|
|
homeserver it turns on server-side key backup and secret storage
|
|
(log line `matrix recovery enabled`), unless the account already has
|
|
secret storage, which it never replaces (a `WARN` instead). With a
|
|
backup there it recovers from it (`recovered from the server-side key backup`), which downloads the
|
|
room keys and signs the new device with the account's existing
|
|
cross-signing identity. A crypto store lost to `--purge` or to a
|
|
primary-token reset therefore comes back with its history. With no passphrase
|
|
stored yet the account comes up without backup and picks it up on the
|
|
next daemon start. A failure is a `WARN` and never stops the daemon.
|
|
|
|
The module's retired hive-side code, its evaluation assertions and the
|
|
fluffychat-web build fixes are in the
|
|
[implementation notes](../../4-implementation/networking/matrix-homeserver.md).
|