Watch
0
0
Fork
You've already forked hyperhive
0

keep the matrix appservice registration in bao, with the host file as the last copy

This commit is contained in:
atlas 2026-10-27 13:52:17 +01:00
commit d5c5862654
2 changed files with 1014 additions and 273 deletions

View file

@ -137,11 +137,12 @@ let
#
# The homeserver's one appservice registration, and the swarm's identity on
# it: `swarm-controller` creates every agent's account and every hive's
# `@hive-<hive>` sender with its token. Minted INSIDE this container by
# `swarm-matrix-ctl appservice render` and published to the store by
# `appservice publish`, which is why it is gated on matrix-ctl's store
# identity: with nobody to publish it, a registration here would be an
# admin credential nobody reads.
# `@hive-<hive>` sender with its token. The store holds its tokens;
# `swarm-matrix-ctl appservice sync` keeps this container's copy in step with
# them, which is why it is gated on matrix-ctl's store identity: with nobody
# to sync it, a registration here would be an admin credential nobody reads.
# `appservice render` minted a copy here only for a host that has none, and
# renders the one the homeserver starts from.
#
# Its sender is promoted to homeserver admin at boot (`admin_execute`
# below), so its token goes only to a store path no hive's policy reaches.
@ -1002,9 +1003,11 @@ in
# below is `requiredBy` it and local only.
++ lib.optional ctlActive "${swarmAppserviceCredentialId}:${swarmAppserviceDir}/swarm.yaml";
# The swarm registration, minted and rendered before the homeserver
# loads it. No network and no store: this is on tuwunel's start
# path, and a store outage must not keep the homeserver down.
# The swarm registration, rendered before the homeserver loads it from
# the tokens in the state dir: the last copy of the store's that
# `swarm-matrix-appservice-sync` took, or a minted pair on a host with
# none. No network and no store: this is on tuwunel's start path, and
# a store outage must not keep the homeserver down.
systemd.services.swarm-matrix-appservice-render = lib.mkIf ctlActive {
description = "render the swarm's appservice registration";
before = [ "tuwunel.service" ];
@ -1027,21 +1030,31 @@ in
};
};
# Hand the swarm registration's token to swarm-controller, through
# the store, under matrix-ctl's identity; the write lands at a path
# only matrix-ctl and the controller are granted (./swarm-bao.nix).
# The store holds the swarm registration; this keeps the state dir
# the render above reads in step with it, and publishes the
# `as_token` to the path `swarm-controller` reads, under matrix-ctl's
# identity (the grants are in ./swarm-bao.nix). A store without the
# registration is seeded from the state dir, so nothing rotates.
#
# ⚠️ Not `before`/`requiredBy` tuwunel, and nothing orders tuwunel
# after it: the homeserver starts from the state dir's last copy
# whether or not the store answers. When the registration is newer than
# tuwunel's last start, this restarts tuwunel onto it.
#
# `Restart=on-failure` covers a sealed store or one still starting;
# the start timeout is bounded for the reason
# the timer below repeats the run, which a unit without `RemainAfterExit` allows.
# The start timeout is bounded for the reason
# ./swarm-secret-publisher.nix states: a sealed store answers on the
# port and never answers the read.
systemd.services.swarm-matrix-appservice-publish = lib.mkIf ctlActive {
description = "publish the swarm's appservice token to the swarm secret store";
systemd.services.swarm-matrix-appservice-sync = lib.mkIf ctlActive {
description = "keep the swarm's appservice registration in step with the swarm secret store";
after = [ "swarm-matrix-appservice-render.service" ];
requires = [ "swarm-matrix-appservice-render.service" ];
wantedBy = [ "multi-user.target" ];
path = [ pkgs.systemd ];
serviceConfig = {
Type = "oneshot";
ExecStart = "${deployCfg.matrix.ctlPackage}/bin/swarm-matrix-ctl appservice publish";
ExecStart = "${deployCfg.matrix.ctlPackage}/bin/swarm-matrix-ctl appservice sync";
Restart = "on-failure";
RestartSec = 30;
TimeoutStartSec = 60;
@ -1053,12 +1066,19 @@ in
BAO_CLIENT_KEY = deployCfg.matrix.ctlBaoClientKeyFile;
MATRIX_APPSERVICE_CERT_ROLE = ctlCertRole;
MATRIX_APPSERVICE_DIR = swarmAppserviceDir;
MATRIX_APPSERVICE_SENDER = swarmSenderLocalpart;
MATRIX_APPSERVICE_USER_REGEX = appserviceUserRegex;
MATRIX_APPSERVICE_RESTART_UNIT = "tuwunel.service";
}
// lib.optionalAttrs (deployCfg.bao.serverCaFile != null) {
BAO_CACERT = deployCfg.bao.serverCaFile;
};
};
systemd.timers.swarm-matrix-appservice-sync = lib.mkIf ctlActive (
storeRefreshTimer "re-read the swarm's appservice registration from the swarm secret store"
);
environment.systemPackages = [ deployCfg.matrix.package ];
};
};

View file

@ -618,7 +618,7 @@ let
matrixCtlPolicyName = "swarm-matrix-ctl";
matrixCtlCn = baoDeploy.matrixCtlCommonName;
# Three paths, each spelled to the leaf, and every narrowing in them is
# Four paths, each spelled to the leaf, and every narrowing in them is
# load-bearing.
#
# `secret/data/` is KV v2's ACL prefix, inserted by the engine rather than
@ -628,18 +628,27 @@ let
# to overwrite Grafana's OIDC client. The paths are spelled to the leaf for
# that reason, not for tidiness.
#
# The first is the swarm appservice's token, which matrix-ctl mints inside
# the container and publishes here for the controller. `read` as well as
# write, unlike either sibling, because publish compares before it writes.
# The first is the swarm appservice's token, which matrix-ctl derives from
# the registration and publishes here for the controller. `read` as well as
# write, unlike either sibling, because sync compares before it writes.
#
# The second is the homeserver's own OIDC client secret, read-only:
# The second is the registration's tokens: the store's copy, which matrix-ctl
# seeds from the container's state dir and then follows. `create` and `read`
# only, never `update`: a registration that exists is replaced by an
# operator, not by a unit.
#
# The third is the homeserver's own OIDC client secret, read-only:
# `swarm-bao-matrix-oidc` on the homeserver's host reads it under this leaf.
# The third, also read-only, is the client secret of the journal forwarder in
# The fourth, also read-only, is the client secret of the journal forwarder in
# the homeserver's container.
matrixCtlPolicyText = ''
path "${credentialMountPath}/data/${swarmAppserviceTokenLeaf}" {
capabilities = ["create", "update", "read"]
}
path "${credentialMountPath}/data/${swarmAppserviceRegistrationLeaf}" {
capabilities = ["create", "read"]
}
''
+ readStanza "${credentialMountPath}/data/swarm/services/${hyperhiveCfg.swarm.matrix.sso.clientId}/oidc/client"
+ readStanza "${credentialMountPath}/data/swarm/services/${hyperhiveCfg.swarm.matrix.otel.clientId}/oidc/client";
@ -650,6 +659,11 @@ let
# homeserver's admin.
swarmAppserviceTokenLeaf = "swarm/controller/swarm-controller/matrix/appservice-token";
# The swarm appservice registration's leaf, the nix half of
# `swarm_secret_client::matrix::swarm_appservice_registration_path`. Only
# matrix-ctl reaches it; the controller reads the token above, not this.
swarmAppserviceRegistrationLeaf = "swarm/controller/swarm-controller/matrix/appservice-registration";
# The controller's own OIDC client secret, the nix half of
# `swarm_secret_client::queue::controller_client_path`. The publisher writes
# it; the controller reads it and holds no other copy.
@ -1902,12 +1916,13 @@ in
example = "swarm-matrix-ctl.svc";
description = ''
Subject the store's matrix-ctl cert-auth role accepts — the
identity the matrix container's `swarm-matrix-appservice-publish`
unit presents when it publishes the swarm appservice's token.
identity the matrix container's `swarm-matrix-appservice-sync`
unit presents when it syncs the swarm appservice's registration and
publishes its token.
A **third** identity rather than reuse of either sibling above. Its
grant is that credential itself, plus read on two OIDC client
secrets: the homeserver's, fetched by `swarm-bao-matrix-oidc`, and
grant is that credential and the registration it derives from, plus
read on two OIDC client secrets: the homeserver's, fetched by `swarm-bao-matrix-oidc`, and
that of the journal forwarder in the homeserver's container. The
point of the whole arrangement is that the
process holding the appservice token is not the hive, so handing it