docs: the forge, knowledge, archetypes, matrix and SSO pages move their implementation detail out
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.
This commit is contained in:
parent
c6b24359cd
commit
0f39bdaaa4
1 changed files with 708 additions and 563 deletions
193
docs/4-implementation/swarm/matrix-provisioning.md
Normal file
193
docs/4-implementation/swarm/matrix-provisioning.md
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
# 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).
|
||||
Loading…
Reference in a new issue