hive-matrix: load the swarm's appservice and promote its sender
The matrix container renders the swarm registration before tuwunel starts (`requiredBy` it, no network), tuwunel loads it as a second `.yaml` credential, and a publish unit hands its token to the store. tuwunel 1.9.1 refuses only a duplicate id or as_token, not overlapping non-exclusive namespaces, so it sits beside the hive's `hyperhive` registration. `admin_execute` promotes exactly `@swarm` at boot. The module-eval pin narrows from "no account" to that one list, compared whole so a second entry fails; `admin_execute_errors_ignore` is pinned too. matrix-ctl may write the swarm token's leaf, and the controller may only read it. Everything is gated on matrix-ctl's store identity: with nobody to publish the token, the registration would be an admin credential nobody reads.
This commit is contained in:
parent
89aff8d613
commit
9308752a09
4 changed files with 194 additions and 25 deletions
|
|
@ -172,6 +172,26 @@ let
|
|||
isReadOnly = true;
|
||||
});
|
||||
|
||||
# ── the swarm's own appservice ──────────────────────────────────────
|
||||
#
|
||||
# A second registration beside the hive's `hyperhive` one, and the swarm's
|
||||
# identity on this homeserver: `swarm-controller` creates every agent's
|
||||
# account 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 the same identity as
|
||||
# the sender mint: with nobody to publish it, a registration here would be
|
||||
# an admin credential nobody reads.
|
||||
#
|
||||
# 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.
|
||||
# `swarm` is in ../reserved-names.nix, so no agent can be created as this
|
||||
# account.
|
||||
swarmSenderLocalpart = "swarm";
|
||||
# Inside the container: the render unit's `StateDirectory`. `ephemeral =
|
||||
# false` keeps it across restarts, so "mint when absent" is mint once.
|
||||
swarmAppserviceDir = "/var/lib/swarm-matrix-appservice";
|
||||
swarmAppserviceCredentialId = "swarm-appservice.yaml";
|
||||
|
||||
# Where a reader of the published credential is told the token is good for.
|
||||
# Empty when this hive serves no vhost: `matrix::Credential.homeserver` is an
|
||||
# `Option`, and matrix-ctl reads an empty variable as absent rather than as
|
||||
|
|
@ -1304,21 +1324,27 @@ in
|
|||
# `Services::start()`, before the listener accepts anything.
|
||||
appservice_dir = appserviceCredentialDir;
|
||||
|
||||
# No `admin_execute` promotion for `@${hiveLocalpart}`. The
|
||||
# hive's account is an ordinary user: it creates the hive
|
||||
# Space and chat room and invites agents into them, all of
|
||||
# which ride on being the rooms' own creator at power level
|
||||
# 100, and none of which is a homeserver-admin capability.
|
||||
# Granting it server admin at boot would hand a credential
|
||||
# that every hive reads far more than the work needs.
|
||||
#
|
||||
# The two operations that do need an admin sender —
|
||||
# `!admin users make-user-admin` and
|
||||
# `!admin users reset-password`, both messages into
|
||||
# `#admins:${effectiveServerName}` — therefore have no
|
||||
# working sender here. They are swarm-level operations and
|
||||
# are being rehomed as such; until then they fail, loudly,
|
||||
# rather than being served by an over-privileged token.
|
||||
}
|
||||
# Exactly one account is promoted to homeserver admin at boot:
|
||||
# the swarm appservice's sender. Its token is read by
|
||||
# matrix-ctl and swarm-controller only. `@${hiveLocalpart}`
|
||||
# stays an ordinary user, because every hive reads its
|
||||
# credential; what it does (the Space, the chat room, the
|
||||
# invites) rides on being the rooms' creator, not on admin.
|
||||
#
|
||||
# The registration load creates the sender inside
|
||||
# `Services::start()`, and `admin_execute` runs after that and
|
||||
# before the listener accepts anything. `make-user-admin` is a
|
||||
# no-op on an account that is already admin.
|
||||
# `admin_execute_errors_ignore` is load-bearing: a failing
|
||||
# command aborts startup when it is false.
|
||||
// lib.optionalAttrs ctlActive {
|
||||
admin_execute = [
|
||||
"users make-user-admin @${swarmSenderLocalpart}:${effectiveServerName}"
|
||||
];
|
||||
admin_execute_errors_ignore = true;
|
||||
}
|
||||
// {
|
||||
# Server-side E2EE is opt-in (default off); the agent matrix
|
||||
# client always supports decryption regardless.
|
||||
allow_encryption = cfg.allowEncryption;
|
||||
|
|
@ -1387,7 +1413,13 @@ in
|
|||
# every OAuth exchange, not just at startup, so it has to
|
||||
# outlive the unit's start — a credentials path does.
|
||||
"oidc_client_secret:${toString deployCfg.matrix.sso.clientSecretFile}"
|
||||
];
|
||||
]
|
||||
# The swarm registration, a second `.yaml` in the same directory:
|
||||
# tuwunel loads every one it finds (`appservice/mod.rs`), and
|
||||
# refuses only a duplicate `id` or `as_token`, never an
|
||||
# overlapping namespace. A missing source fails this unit, which
|
||||
# is why the render below is `requiredBy` it and local only.
|
||||
++ lib.optional ctlActive "${swarmAppserviceCredentialId}:${swarmAppserviceDir}/swarm.yaml";
|
||||
|
||||
# Publish the appservice sender account's access token to the swarm
|
||||
# store, once, under an identity that belongs to this container and
|
||||
|
|
@ -1449,6 +1481,61 @@ in
|
|||
};
|
||||
};
|
||||
|
||||
# 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.
|
||||
systemd.services.swarm-matrix-appservice-render = lib.mkIf ctlActive {
|
||||
description = "render the swarm's appservice registration";
|
||||
before = [ "tuwunel.service" ];
|
||||
requiredBy = [ "tuwunel.service" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
ExecStart = "${deployCfg.matrix.ctlPackage}/bin/swarm-matrix-ctl appservice render";
|
||||
StateDirectory = baseNameOf swarmAppserviceDir;
|
||||
StateDirectoryMode = "0700";
|
||||
UMask = "0077";
|
||||
SyslogIdentifier = "swarm-matrix-appservice";
|
||||
};
|
||||
environment = {
|
||||
MATRIX_APPSERVICE_DIR = swarmAppserviceDir;
|
||||
MATRIX_APPSERVICE_SENDER = swarmSenderLocalpart;
|
||||
# The hive registration's namespace. Agents' localparts are
|
||||
# bare `[a-z0-9-]`, so nothing narrower covers them without
|
||||
# also covering people.
|
||||
MATRIX_APPSERVICE_USER_REGEX = appserviceUserRegex;
|
||||
};
|
||||
};
|
||||
|
||||
# Hand the swarm registration's token to swarm-controller, through
|
||||
# the store. Same identity and retry shape as `swarm-matrix-ctl`
|
||||
# above; the write lands at a path only matrix-ctl and the
|
||||
# controller are granted (./swarm-bao.nix).
|
||||
systemd.services.swarm-matrix-appservice-publish = lib.mkIf ctlActive {
|
||||
description = "publish the swarm's appservice token to the swarm secret store";
|
||||
after = [ "swarm-matrix-appservice-render.service" ];
|
||||
requires = [ "swarm-matrix-appservice-render.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${deployCfg.matrix.ctlPackage}/bin/swarm-matrix-ctl appservice publish";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 30;
|
||||
TimeoutStartSec = 60;
|
||||
SyslogIdentifier = "swarm-matrix-appservice";
|
||||
};
|
||||
environment = {
|
||||
BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}";
|
||||
BAO_CLIENT_CERT = deployCfg.matrix.ctlBaoClientCertFile;
|
||||
BAO_CLIENT_KEY = deployCfg.matrix.ctlBaoClientKeyFile;
|
||||
MATRIX_APPSERVICE_CERT_ROLE = ctlCertRole;
|
||||
MATRIX_APPSERVICE_DIR = swarmAppserviceDir;
|
||||
}
|
||||
// lib.optionalAttrs (deployCfg.bao.serverCaFile != null) {
|
||||
BAO_CACERT = deployCfg.bao.serverCaFile;
|
||||
};
|
||||
};
|
||||
|
||||
environment.systemPackages = [ deployCfg.matrix.package ];
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue