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
|
|
@ -334,12 +334,18 @@ let
|
|||
# are what keep it from drifting back — neither the `services/*` tree nor
|
||||
# a `hives/*` wildcard may appear, since either one hands matrix-ctl (or
|
||||
# a hive) reach beyond the single leaf it owns.
|
||||
name = "matrix-ctl's grant is one hive's sender token path and nothing else";
|
||||
#
|
||||
# The one other leaf is the swarm appservice token, which matrix-ctl
|
||||
# mints and publishes for the controller. Counted, so a third stanza
|
||||
# fails rather than riding along beside two correct ones.
|
||||
name = "matrix-ctl's grant is one hive's sender token and the swarm appservice token, nothing else";
|
||||
ok =
|
||||
let
|
||||
s = baoGrantHere.systemd.services.swarm-bao-matrix-ctl-policy.script;
|
||||
in
|
||||
lib.hasInfix "path \"secret/data/swarm/hives/h1/matrix/sender-token\" {" s
|
||||
&& lib.hasInfix "path \"secret/data/swarm/controller/swarm-controller/matrix/appservice-token\" {\n capabilities = [\"create\", \"update\", \"read\"]\n}" s
|
||||
&& lib.length (lib.splitString "path \"" s) == 3
|
||||
&& !(lib.hasInfix "secret/data/swarm/services" s)
|
||||
&& !(lib.hasInfix "secret/data/swarm/agents" s)
|
||||
&& !(lib.hasInfix "secret/data/swarm/hives/*" s)
|
||||
|
|
@ -747,6 +753,14 @@ let
|
|||
in
|
||||
lib.hasInfix "path \"secret/data/swarm/agents/*\" {\n capabilities = [\"create\", \"read\", \"update\"]" s;
|
||||
}
|
||||
{
|
||||
# The swarm appservice token is a homeserver-admin credential. The
|
||||
# controller mints agents' accounts with it and has no business replacing
|
||||
# it: matrix-ctl is its one writer. Pinned as the whole stanza, so an
|
||||
# added capability fails.
|
||||
name = "the controller reads the swarm appservice token and cannot write it";
|
||||
ok = lib.hasInfix "path \"secret/data/swarm/controller/swarm-controller/matrix/appservice-token\" {\n capabilities = [\"read\"]\n}" baoGrantHere.systemd.services.swarm-bao-controller-policy.script;
|
||||
}
|
||||
{
|
||||
# The policy above grants paths under a mount nothing else creates, so
|
||||
# the unit that writes the policy has to create it too — otherwise every
|
||||
|
|
|
|||
|
|
@ -392,18 +392,64 @@ let
|
|||
&& !(matrixNoBaoIdentity.containers.hive-matrix.bindMounts ? "/var/lib/swarm-bao-pki");
|
||||
}
|
||||
{
|
||||
# 🩸 The privilege arm of the credential this slice publishes: the
|
||||
# account it belongs to must not be a homeserver admin. Read on the
|
||||
# rendered homeserver settings rather than on an option, because the
|
||||
# grant was never an option — it was a boot command in `admin_execute`,
|
||||
# and a command list is exactly the shape a later edit re-adds without
|
||||
# anything noticing.
|
||||
name = "the homeserver promotes no account to admin at boot";
|
||||
# 🩸 The privilege arm: exactly one account is a homeserver admin, the
|
||||
# swarm appservice's sender, whose token only matrix-ctl and the
|
||||
# controller read. Read on the rendered settings rather than on an
|
||||
# option, because the grant is a boot command in `admin_execute`, and a
|
||||
# command list is exactly the shape a later edit extends without anything
|
||||
# noticing — so the list is compared whole, and a second entry fails.
|
||||
name = "the homeserver promotes exactly the swarm sender to admin at boot, and no hive's sender";
|
||||
ok =
|
||||
let
|
||||
g = baoWithMatrix.containers.hive-matrix.config.services.matrix-tuwunel.settings.global;
|
||||
in
|
||||
!(g ? admin_execute) || g.admin_execute == [ ];
|
||||
g.admin_execute == [ "users make-user-admin @swarm:${g.server_name}" ]
|
||||
&& !(lib.any (c: lib.hasInfix "@hive-" c) g.admin_execute);
|
||||
}
|
||||
{
|
||||
# Load-bearing rather than lenient: tuwunel aborts startup on a failing
|
||||
# `admin_execute` command when this is false, and the homeserver must not
|
||||
# stay down over a promotion.
|
||||
name = "a failed admin promotion does not stop the homeserver";
|
||||
ok =
|
||||
baoWithMatrix.containers.hive-matrix.config.services.matrix-tuwunel.settings.global.admin_execute_errors_ignore
|
||||
or false;
|
||||
}
|
||||
{
|
||||
# The swarm registration reaches tuwunel the way the hive's does: a
|
||||
# `.yaml` credential in the directory `appservice_dir` names.
|
||||
name = "tuwunel loads the swarm registration as a yaml credential";
|
||||
ok = lib.elem "swarm-appservice.yaml:/var/lib/swarm-matrix-appservice/swarm.yaml" (
|
||||
baoWithMatrix.containers.hive-matrix.config.systemd.services.tuwunel.serviceConfig.LoadCredential
|
||||
);
|
||||
}
|
||||
{
|
||||
# A missing `LoadCredential` source fails tuwunel, so the render has to
|
||||
# have run first — and it is local only, so it cannot fail on a store.
|
||||
name = "the swarm registration is rendered before tuwunel and required by it, without the store";
|
||||
ok =
|
||||
let
|
||||
r = baoWithMatrix.containers.hive-matrix.config.systemd.services.swarm-matrix-appservice-render;
|
||||
in
|
||||
lib.elem "tuwunel.service" r.before
|
||||
&& lib.elem "tuwunel.service" r.requiredBy
|
||||
&& lib.hasSuffix "swarm-matrix-ctl appservice render" r.serviceConfig.ExecStart
|
||||
&& !(r.environment ? BAO_ADDR);
|
||||
}
|
||||
{
|
||||
# The absence arm for the four above: with no matrix-ctl identity there
|
||||
# is nobody to publish the swarm token, so no admin, no registration and
|
||||
# no render — rather than an admin credential nobody reads.
|
||||
name = "a matrix container with no store identity has no swarm appservice and promotes nobody";
|
||||
ok =
|
||||
let
|
||||
c = matrixNoBaoIdentity.containers.hive-matrix.config;
|
||||
g = c.services.matrix-tuwunel.settings.global;
|
||||
in
|
||||
!(g ? admin_execute)
|
||||
&& !(c.systemd.services ? swarm-matrix-appservice-render)
|
||||
&& !(c.systemd.services ? swarm-matrix-appservice-publish)
|
||||
&& !(lib.any (lib.hasPrefix "swarm-appservice.yaml") c.systemd.services.tuwunel.serviceConfig.LoadCredential);
|
||||
}
|
||||
{
|
||||
# 🩸 The arm the whole refusal exists for. Both readers are gated on their
|
||||
|
|
|
|||
Loading…
Reference in a new issue