matrix: drop the per-hive appservice and its unused token
Every hive's homeserver loaded a per-hive `hyperhive` appservice
registration, and the swarm minted and delivered an as_token for it
through bao. No client presented that token: swarm-controller creates
every `@hive-<hive>` sender and every agent account with the swarm
appservice token (hive_sender.rs, agent_token.rs), and no Rust code read
the per-hive one.
Removed, with everything that existed only to serve it:
- hive-matrix.nix: the `hyperhive` registration, its activation-time
render, the host bind mount of /var/lib/hyperhive/matrix-appservice,
its LoadCredential entry, and the internal options
`deploy.matrix.appserviceTokenFile` / `appserviceRegistrationScript`.
tuwunel now loads only the swarm registration.
- glue-matrix-bao-token.nix and its `swarm-bao-matrix-token` unit.
- glue-bao-tls.nix: the matrix-token leaf and its defaults.
- swarm-bao.nix: the `swarm-matrix-token-<hive>` roles and policies
(`swarm-bao-matrix-token-policy`), and the options
`deploy.bao.matrixTokenClient{Cert,Key}File` and
`matrixTokenCommonNamePrefix`, now mkRemovedOptionModule.
- swarm-secret-publisher.nix: the per-hive token mint and its state dir.
- swarm.nix: the reserved `<matrixTokenCommonNamePrefix>-<hive>` CN.
- swarm-secret-client: `matrix::appservice_token_path`.
The swarm registration (swarm-matrix-ctl, swarm-controller) is untouched.
Comments and docs that pointed at the removed glue file now point at
glue-queue-agent-credential.nix, which has the same reader shape.
Refs #4428
This commit is contained in:
parent
e4b170f8c6
commit
48067d07e8
31 changed files with 362 additions and 1233 deletions
|
|
@ -34,16 +34,14 @@ let
|
|||
# inexpressible — the gate asked whether the store was a neighbour.
|
||||
#
|
||||
# 🩸 One leaf PER READER, and this fixture is where that cost is visible: the
|
||||
# two units below identify themselves to the store separately, so an operator
|
||||
# placing leaves by hand places one for each rather than one for both. Naming
|
||||
# only `clientCertFile` here would leave neither unit rendered — which is what
|
||||
# the arms below would then be asserting about.
|
||||
# queue-credential unit identifies itself to the store separately from the
|
||||
# hive, so an operator placing leaves by hand places one for it as well as
|
||||
# the hive's own. Naming only `clientCertFile` here would leave the unit
|
||||
# unrendered — which is what the arms below would then be asserting about.
|
||||
baoRemoteReader = hive {
|
||||
deploy.matrix.enable = true;
|
||||
deploy.bao.clientCertFile = "/etc/pki/bao-client.pem";
|
||||
deploy.bao.clientKeyFile = "/etc/pki/bao-client-key.pem";
|
||||
deploy.bao.matrixTokenClientCertFile = "/etc/pki/bao-matrix-token.pem";
|
||||
deploy.bao.matrixTokenClientKeyFile = "/etc/pki/bao-matrix-token-key.pem";
|
||||
deploy.bao.queueAgentClientCertFile = "/etc/pki/bao-queue-agent.pem";
|
||||
deploy.bao.queueAgentClientKeyFile = "/etc/pki/bao-queue-agent-key.pem";
|
||||
};
|
||||
|
|
@ -54,24 +52,19 @@ let
|
|||
# fixture set its own, as ./lib.nix asks.
|
||||
matrixNoBaoIdentity = hive { deploy.matrix.enable = true; };
|
||||
|
||||
# 🩸 The hand-configured remote reader that named seven of the eight options.
|
||||
# 🩸 The hand-configured remote reader that named the hive's leaf and stopped.
|
||||
# `baoRemoteReader` above is the same deployment done right; this one holds
|
||||
# the hive's own leaf, so it demonstrably reads the store, and is missing both
|
||||
# per-principal pairs. Before the four-way split this host rendered both units
|
||||
# off `clientCertFile` alone, so what it has now is a silent regression rather
|
||||
# than any shape an operator chose — which is what the refusal arms below are
|
||||
# about. Kept as one fixture rather than two because both refusals fire on it
|
||||
# and each arm names which.
|
||||
# the hive's own leaf, so it demonstrably reads the store, and is missing the
|
||||
# queue reader's pair. Without the refusal it would render no queue reader
|
||||
# and say nothing — which is what the refusal arms below are about.
|
||||
baoRemoteReaderMissingLeaves = hive {
|
||||
deploy.matrix.enable = true;
|
||||
deploy.bao.clientCertFile = "/etc/pki/bao-client.pem";
|
||||
deploy.bao.clientKeyFile = "/etc/pki/bao-client-key.pem";
|
||||
};
|
||||
|
||||
# The same omission on a hive that does NOT run a homeserver. Separates the
|
||||
# matrix refusal's `deploy.matrix.enable` clause from the queue refusal, which
|
||||
# checks only the hive's own toggle — an arm below reads exactly one refusal
|
||||
# off it.
|
||||
# The same omission on a hive that does NOT run a homeserver: the queue
|
||||
# refusal checks only the hive's own toggle.
|
||||
remoteReaderNoMatrixMissingLeaves = hive {
|
||||
deploy.bao.clientCertFile = "/etc/pki/bao-client.pem";
|
||||
deploy.bao.clientKeyFile = "/etc/pki/bao-client-key.pem";
|
||||
|
|
@ -92,10 +85,10 @@ let
|
|||
# holding this host's role — all of which a retry fixes. A read that
|
||||
# answers "nothing there" is not, so only the first is allowed to fail
|
||||
# the unit.
|
||||
name = "the matrix token reader retries a failed login and still degrades on an empty read";
|
||||
name = "the queue credential reader retries a failed login and still degrades on an empty read";
|
||||
ok =
|
||||
let
|
||||
u = baoWithMatrix.systemd.services.swarm-bao-matrix-token;
|
||||
u = baoWithMatrix.systemd.services.swarm-bao-queue-agent;
|
||||
# Everything between the login's failure branch and the read's, which
|
||||
# is where the exit that decides "retry or give up" lives.
|
||||
afterLogin = lib.last (lib.splitString "bao login" u.script);
|
||||
|
|
@ -109,30 +102,15 @@ let
|
|||
&& lib.hasInfix "exit 0" (lib.last (lib.splitString "bao kv get" u.script));
|
||||
}
|
||||
{
|
||||
# The reader's own grant covers `swarm/hives/<this hive>/*` and
|
||||
# `swarm/agents/*`; a path outside those answers 403, not "no such key".
|
||||
# So the hive segment is what makes the read reachable, and a rename that
|
||||
# drops it looks correct and fails identically on every boot.
|
||||
name = "the matrix token path sits inside the prefix the reader is granted";
|
||||
ok =
|
||||
let
|
||||
s = baoWithMatrix.systemd.services.swarm-bao-matrix-token.script;
|
||||
in
|
||||
lib.hasInfix "secret/swarm/hives/" s
|
||||
&& lib.hasInfix "/matrix/appservice-token" s
|
||||
# The shape it used to have: `matrix` where a principal kind belongs,
|
||||
# which no grant covers.
|
||||
&& !(lib.hasInfix "secret/swarm/matrix/" s);
|
||||
}
|
||||
{
|
||||
# The store's second reader, and the gate that decides it exists is the
|
||||
# certificate rather than anything about agents: containers are created
|
||||
# The gate that decides this reader exists is the certificate rather
|
||||
# than anything about agents: containers are created
|
||||
# at runtime, so there is no static "this hive runs agents" fact to ask.
|
||||
name = "a hive that names a client identity reads its agent queue credential";
|
||||
ok = baoRemoteReader.systemd.services ? swarm-bao-queue-agent;
|
||||
}
|
||||
{
|
||||
# Same 403-not-a-miss reason as the matrix arm above, against the path
|
||||
# The reader's own grant covers `swarm/hives/<this hive>/*`; a path
|
||||
# outside it answers 403, not "no such key". This is the path
|
||||
# `swarm_secret_client::queue::agent_client_path` builds from the same
|
||||
# pieces. The negative arm is the rename this one is exposed to: a
|
||||
# credential named for the queue rather than for the hive that presents
|
||||
|
|
@ -176,17 +154,6 @@ let
|
|||
&& !(builtins.elem "hive-c0re.service" (u.requiredBy or [ ]))
|
||||
&& !(builtins.elem "hive-c0re.service" (u.requires or [ ]));
|
||||
}
|
||||
{
|
||||
# The store's first reader. Its unit belongs to the pairing, not to
|
||||
# either service: matrix must not learn the store exists, and the store
|
||||
# must not know who reads it.
|
||||
name = "a store deployed beside the homeserver fetches its appservice token";
|
||||
ok = baoWithMatrix.systemd.services ? swarm-bao-matrix-token;
|
||||
}
|
||||
{
|
||||
name = "a hive that names a client identity reads from a store it does not run";
|
||||
ok = baoRemoteReader.systemd.services ? swarm-bao-matrix-token;
|
||||
}
|
||||
{
|
||||
# `Requires=` on a unit that does not exist fails the job, and nothing
|
||||
# local mints certificates off-host — so this orders against nothing.
|
||||
|
|
@ -198,7 +165,7 @@ let
|
|||
let
|
||||
s = baoRemoteReader.systemd.services;
|
||||
in
|
||||
s ? swarm-bao-matrix-token && s.swarm-bao-matrix-token.requires == [ ];
|
||||
s ? swarm-bao-queue-agent && s.swarm-bao-queue-agent.requires == [ ];
|
||||
}
|
||||
{
|
||||
# Presence control for the case above: the list is conditional, not gone.
|
||||
|
|
@ -207,17 +174,17 @@ let
|
|||
let
|
||||
s = baoWithMatrix.systemd.services;
|
||||
in
|
||||
s ? swarm-bao-matrix-token && s.swarm-bao-matrix-token.requires == [ "swarm-bao-pki.service" ];
|
||||
s ? swarm-bao-queue-agent && s.swarm-bao-queue-agent.requires == [ "swarm-bao-pki.service" ];
|
||||
}
|
||||
{
|
||||
# Nothing asserted this script before, which is how it kept a branch that
|
||||
# named three states and threw away the only thing telling them apart. A
|
||||
# missing value, a refused identity and an unreachable host all end in the
|
||||
# same degraded mode here, correctly — what must survive is which one.
|
||||
name = "the matrix token reader carries the store's own diagnostic into the journal";
|
||||
name = "the queue credential reader carries the store's own diagnostic into the journal";
|
||||
ok =
|
||||
let
|
||||
s = baoWithMatrix.systemd.services.swarm-bao-matrix-token.script;
|
||||
s = baoWithMatrix.systemd.services.swarm-bao-queue-agent.script;
|
||||
in
|
||||
!(lib.hasInfix "2>/dev/null" s) && lib.hasInfix ''cat "''$err"'' s;
|
||||
}
|
||||
|
|
@ -295,18 +262,12 @@ let
|
|||
# it the unit names two paths the container does not have, and fails at
|
||||
# the TLS handshake naming no cause. Read off the mount table rather than
|
||||
# the option, so a pairing that stops reaching `bindMounts` still fails.
|
||||
#
|
||||
# The second arm is the shape guard: `bindMounts` is one literal plus two
|
||||
# merges, and a rewrite that dropped the appservice registration would
|
||||
# take the homeserver's own credential with it.
|
||||
name = "the matrix container binds matrix-ctl's PKI read-only, without losing the appservice registration";
|
||||
name = "the matrix container binds matrix-ctl's PKI read-only";
|
||||
ok =
|
||||
let
|
||||
mounts = baoWithMatrix.containers.hive-matrix.bindMounts;
|
||||
in
|
||||
mounts ? "/var/lib/swarm-bao-pki"
|
||||
&& mounts."/var/lib/swarm-bao-pki".isReadOnly
|
||||
&& mounts ? "/var/lib/hyperhive/matrix-appservice";
|
||||
mounts ? "/var/lib/swarm-bao-pki" && mounts."/var/lib/swarm-bao-pki".isReadOnly;
|
||||
}
|
||||
{
|
||||
# The cert role ./host-modules/swarm-bao.nix writes, which is the one
|
||||
|
|
@ -366,12 +327,18 @@ let
|
|||
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
|
||||
);
|
||||
# The swarm registration reaches tuwunel as a `.yaml` credential in the
|
||||
# directory `appservice_dir` names, and it is the only registration
|
||||
# there: no client presents a per-hive one's token, so loading one would
|
||||
# be a credential nobody reads.
|
||||
name = "tuwunel loads the swarm registration as a yaml credential, and no other";
|
||||
ok =
|
||||
let
|
||||
creds =
|
||||
baoWithMatrix.containers.hive-matrix.config.systemd.services.tuwunel.serviceConfig.LoadCredential;
|
||||
in
|
||||
lib.elem "swarm-appservice.yaml:/var/lib/swarm-matrix-appservice/swarm.yaml" creds
|
||||
&& lib.length (lib.filter (c: lib.hasInfix ".yaml:" c) creds) == 1;
|
||||
}
|
||||
{
|
||||
# A missing `LoadCredential` source fails tuwunel, so the render has to
|
||||
|
|
@ -402,38 +369,30 @@ let
|
|||
&& !(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
|
||||
# own leaf, so the way this regresses is the build going green and three
|
||||
# units rendering where four should — which no presence check on a
|
||||
# rendered unit can see, because the unit that is missing is the evidence.
|
||||
# Read as a refusal naming each option, so an operator acts on the message
|
||||
# without opening the nix.
|
||||
name = "a remote reader missing the per-principal leaves is refused, naming both options";
|
||||
# 🩸 The arm the whole refusal exists for. The reader is gated on its own
|
||||
# leaf, so the way this regresses is the build going green and the unit
|
||||
# not rendering — which no presence check on a rendered unit can see,
|
||||
# because the unit that is missing is the evidence. Read as a refusal
|
||||
# naming each option, so an operator acts on the message without opening
|
||||
# the nix.
|
||||
name = "a remote reader missing the queue reader's leaf is refused, naming both options";
|
||||
ok =
|
||||
refusedOver baoRemoteReaderMissingLeaves "matrixTokenClientCertFile"
|
||||
&& refusedOver baoRemoteReaderMissingLeaves "matrixTokenClientKeyFile"
|
||||
&& refusedOver baoRemoteReaderMissingLeaves "queueAgentClientCertFile"
|
||||
refusedOver baoRemoteReaderMissingLeaves "queueAgentClientCertFile"
|
||||
&& refusedOver baoRemoteReaderMissingLeaves "queueAgentClientKeyFile";
|
||||
}
|
||||
{
|
||||
# The matrix refusal's own gate, which the queue refusal does not have.
|
||||
# Without this arm the two are indistinguishable on the fixture above.
|
||||
name = "the queue refusal needs no homeserver, and the matrix refusal stays quiet without one";
|
||||
ok =
|
||||
refusedOver remoteReaderNoMatrixMissingLeaves "queueAgentClientCertFile"
|
||||
&& !(refusedOver remoteReaderNoMatrixMissingLeaves "matrixTokenClientCertFile");
|
||||
name = "the queue refusal needs no homeserver";
|
||||
ok = refusedOver remoteReaderNoMatrixMissingLeaves "queueAgentClientCertFile";
|
||||
}
|
||||
{
|
||||
# ⚠️ The arm that keeps the refusal from being worse than the silence it
|
||||
# replaced. A hive with NO store identity is the supported no-store
|
||||
# deployment and also the state an operator passes through bringing a hive
|
||||
# up — neither may fail to evaluate. Asserted as "no refusal names any of
|
||||
# the four options", not as "this one fixture is fine", because the way
|
||||
# this breaks is a gate widened to the principal's leaf alone.
|
||||
# up — neither may fail to evaluate. Asserted as "no refusal names either
|
||||
# option", not as "this one fixture is fine", because the way this breaks
|
||||
# is a gate widened to the principal's leaf alone.
|
||||
name = "a hive with no store identity at all is refused over none of the per-principal leaves";
|
||||
ok = lib.all (option: !(refusedOver matrixNoBaoIdentity option)) [
|
||||
"matrixTokenClientCertFile"
|
||||
"matrixTokenClientKeyFile"
|
||||
"queueAgentClientCertFile"
|
||||
"queueAgentClientKeyFile"
|
||||
];
|
||||
|
|
@ -441,22 +400,13 @@ let
|
|||
{
|
||||
# The other half of the same guard, and the one an operator meets far more
|
||||
# often: on the store's own host ./host-modules/glue-bao-tls.nix mkDefaults
|
||||
# all eight, so there is nothing to name and nothing to refuse. Paired with
|
||||
# every pair, so there is nothing to name and nothing to refuse. Paired with
|
||||
# the fully-named remote reader, which is the same deployment done by hand.
|
||||
name = "neither a store host nor a correctly-named remote reader is refused";
|
||||
ok =
|
||||
lib.all
|
||||
(
|
||||
m:
|
||||
lib.all (option: !(refusedOver m option)) [
|
||||
"matrixTokenClientCertFile"
|
||||
"queueAgentClientCertFile"
|
||||
]
|
||||
)
|
||||
[
|
||||
baoWithMatrix
|
||||
baoRemoteReader
|
||||
];
|
||||
ok = lib.all (m: !(refusedOver m "queueAgentClientCertFile")) [
|
||||
baoWithMatrix
|
||||
baoRemoteReader
|
||||
];
|
||||
}
|
||||
];
|
||||
in
|
||||
|
|
|
|||
Loading…
Reference in a new issue