Watch
0
0
Fork
You've already forked hyperhive
0

bao: drop matrix-ctl's per-hive sender-token grant

swarm-controller is the only minter of swarm/hives/<hive>/matrix/sender-token
since #4820, so the swarm-matrix-ctl policy's first stanza granted a write no
code performs. The policy keeps its one used stanza, the swarm appservice token
that `swarm-matrix-ctl appservice publish` writes. deploy.bao.matrixCtlHiveName
only named the hive in the dropped stanza and goes with it.

hive-matrix.nix no longer calls the per-hive appservice's as_token hive-c0re's
authority: tuwunel loads the registration and creates the sender account, and
no client presents that token.
This commit is contained in:
atlas 2026-09-30 00:50:39 +02:00 • committed by mara
commit b58a0d8ba9
3 changed files with 19 additions and 64 deletions

View file

@ -55,13 +55,13 @@ let
# with no host-side chown or GID pinning. # with no host-side chown or GID pinning.
matrixSecretCredential = "/run/credentials/tuwunel.service/oidc_client_secret"; matrixSecretCredential = "/run/credentials/tuwunel.service/oidc_client_secret";
# How this hive creates matrix accounts: an appservice registration whose # The hive's appservice registration, whose `url` is null. Null is a legal
# `url` is null. Null is a legal `url` (ruma's `Registration` types it # `url` (ruma's `Registration` types it `Option<String>`), and it is the
# `Option<String>`), and it is the whole point — with no URL the homeserver # whole point — with no URL the homeserver
# never calls out, so there is no HTTP service to run and no daemon to # never calls out, so there is no HTTP service to run and no daemon to
# operate. What the registration delivers is the `as_token`: hive-c0re's # operate. tuwunel loads the registration and creates its sender account
# standing authority to create, and log in as, the accounts named by the # (below). No client presents its `as_token`: `swarm-controller` mints the
# namespace below, with no shared registration secret in the picture. # sender's token with the swarm registration's.
appserviceId = "hyperhive"; appserviceId = "hyperhive";
# The appservice's own user, and the account the hive acts as. An # The appservice's own user, and the account the hive acts as. An

View file

@ -472,36 +472,18 @@ let
# written by the caller — the same trap as the two grants above. # written by the caller — the same trap as the two grants above.
# #
# Not `swarm/services/*` like the publisher's: a homeserver is not entitled # Not `swarm/services/*` like the publisher's: a homeserver is not entitled
# to overwrite Grafana's OIDC client. Each path is spelled to the leaf for # to overwrite Grafana's OIDC client. The path is spelled to the leaf for
# that reason, not for tidiness. # that reason, not for tidiness.
# #
# 🩸 And the leaf now carries a HIVE segment, which is the narrowing that # It is the swarm appservice's token, which matrix-ctl mints inside the
# matters: the credential used to live at `swarm/services/matrix/sender-token` # container and publishes here for the controller. `read` as well as write,
# — one value for the whole swarm, under the `services/*` tree every hive's # unlike either sibling, because publish compares before it writes.
# own policy grants read on. Under `swarm/hives/<name>/` the only read grant
# that reaches it is that hive's own stanza, so one hive cannot fetch
# another's. `matrixCtlHive` below is the name this principal may write, and
# it is one hive rather than a `hives/*` wildcard for the same reason.
#
# ⚠️ Nothing presenting this identity writes the first stanza's path:
# `swarm-controller` is the sender token's only minter. The stanza is unused.
#
# The second stanza 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.
matrixCtlPolicyText = '' matrixCtlPolicyText = ''
path "${credentialMountPath}/data/swarm/hives/${matrixCtlHive}/matrix/sender-token" {
capabilities = ["create", "update", "read"]
}
path "${credentialMountPath}/data/${swarmAppserviceTokenLeaf}" { path "${credentialMountPath}/data/${swarmAppserviceTokenLeaf}" {
capabilities = ["create", "update", "read"] capabilities = ["create", "update", "read"]
} }
''; '';
# The hive the unused first stanza above names.
matrixCtlHive = baoDeploy.matrixCtlHiveName;
# The swarm appservice token's leaf, the nix half of # The swarm appservice token's leaf, the nix half of
# `swarm_secret_client::matrix::swarm_appservice_token_path`. Under # `swarm_secret_client::matrix::swarm_appservice_token_path`. Under
# `controller/`, the one kind no hive's policy reads: its sender is the # `controller/`, the one kind no hive's policy reads: its sender is the
@ -780,9 +762,8 @@ let
# One reader per hive in the swarm directory. Written from the directory # One reader per hive in the swarm directory. Written from the directory
# rather than from this host's own name because the policy is written where # rather than from this host's own name because the policy is written where
# the STORE is and the reader runs where its hive is — the same split # the STORE is and the reader runs where its hive is, so this host's name is
# `matrixCtlHiveName` above exists to paper over, answered here by naming # not the reader's.
# every hive instead of asking the operator which one.
perHiveReaders = perHiveReaders =
{ rolePrefix, cnPrefix, ... }@spec: { rolePrefix, cnPrefix, ... }@spec:
lib.mapAttrsToList (hiveName: _: { lib.mapAttrsToList (hiveName: _: {
@ -1611,19 +1592,6 @@ in
''; '';
}; };
matrixCtlHiveName = lib.mkOption {
type = lib.types.str;
default = toString hyperhiveCfg.hiveName;
defaultText = lib.literalExpression "services.hyperhive.hiveName";
example = "pr1ma";
description = ''
Hive whose matrix sender token the store's matrix-ctl role may write.
Unused: nothing presenting that identity writes the sender token;
`swarm-controller` is its only minter.
'';
};
matrixTokenCommonNamePrefix = lib.mkOption { matrixTokenCommonNamePrefix = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "swarm-bao-matrix-token"; default = "swarm-bao-matrix-token";

View file

@ -422,32 +422,19 @@ let
# the `services/` prefix the publisher holds would be a real loss even # the `services/` prefix the publisher holds would be a real loss even
# though it would read as tidier. # though it would read as tidier.
# #
# ⚠️ `hives` is PLURAL, because the path segment comes from # The leaf is the swarm appservice token, which matrix-ctl mints and
# `Kind::Hive`'s strum serialisation and not from `Kind::label`, which # publishes for the controller. Counted, so a second stanza fails rather
# renders the singular for error text. The singular spelling evaluates, # than riding along beside a correct one.
# deploys, and 403s every read with "permission denied" and nothing else. name = "matrix-ctl's grant is the swarm appservice token, nothing else";
#
# 🩸 The hive NAME in the middle is the per-hive half of this credential:
# the token used to be one swarm-wide value under `services/matrix/`,
# which every hive's own policy granted read on. The negative arms below
# 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.
#
# 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 = ok =
let let
s = baoGrantHere.systemd.services.swarm-bao-matrix-ctl-policy.script; s = baoGrantHere.systemd.services.swarm-bao-matrix-ctl-policy.script;
in 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.hasInfix "path \"secret/data/swarm/controller/swarm-controller/matrix/appservice-token\" {\n capabilities = [\"create\", \"update\", \"read\"]\n}" s && lib.length (lib.splitString "path \"" s) == 2
&& lib.length (lib.splitString "path \"" s) == 3
&& !(lib.hasInfix "secret/data/swarm/services" s) && !(lib.hasInfix "secret/data/swarm/services" s)
&& !(lib.hasInfix "secret/data/swarm/agents" s) && !(lib.hasInfix "secret/data/swarm/agents" s)
&& !(lib.hasInfix "secret/data/swarm/hives/*" s) && !(lib.hasInfix "secret/data/swarm/hives" s)
&& !(lib.hasInfix "sys/policies/acl" s); && !(lib.hasInfix "sys/policies/acl" s);
} }
{ {