Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/module-eval/bao-matrix-reader.nix
atlas 868c6cf96c drop the shared hive agent login
Every agent now connects to the queue and reads logs with its own secret
from the store (#5181), so the per-hive `hive-<h>-agent` OIDC client has
no reader left. Remove it end to end:

- hive-c0re no longer forwards `hive-queue-agent-{secret,client-id}` into
  containers, and no longer forwards HIVE_AGENT_OIDC_TOKEN_ENDPOINT; the
  agent module that loaded them (nix/agent-modules/queue.nix) is gone, its
  options refused with a removal message.
- The host reader (glue-queue-agent-credential.nix, swarm-bao-queue-agent),
  its store role and policy unit (swarm-bao-queue-agent-policy), its PKI
  leaf and the deploy.bao.queueAgent* / queue.agentCredentialDir options
  are gone; the options are refused with a removal message.
- authelia mints no `hive-<h>-agent` client, and swarm-secret-publish no
  longer writes swarm/hives/<h>/queue/agent.
- swarm-secret-client drops queue::agent_client_path / queue::Credential.
- HIVE_AGENT_NATS_URL is now gated on the agents' queue address alone.

The log store's authelia check stays for operators. swarm-nats-auth still
refuses a `-agent` client id; that refusal and `agentClientSuffix` are left
for a follow-up.

Refs #4459
Refs #4765
2026-10-24 14:00:26 +02:00

285 lines
13 KiB
Nix

# `checks.module-eval-bao-matrix-reader` — see ./lib.nix for the shared
# rationale (why this suite exists, naming convention, "evaluates
# not executes").
{
pkgs,
lib,
self,
nixosSystem,
shared,
}:
let
inherit
(import ./lib.nix {
inherit
pkgs
lib
self
nixosSystem
;
})
hive
hiveWithController
runGroup
;
# The store and a service that reads from it, versus the store alone. The
# pair is what makes the reader's absence arm mean anything.
inherit (shared) baoWithMatrix;
# A hive that reads from a store it does not run: no `deploy.bao.enable`, so
# nothing here mints a leaf and the operator names one placed by hand. The
# deployment this pairing exists to serve, and the one that was previously
# inexpressible — the gate asked whether the store was a neighbour.
baoRemoteReader = hiveWithController {
deploy.matrix.enable = true;
deploy.bao.clientCertFile = "/etc/pki/bao-client.pem";
deploy.bao.clientKeyFile = "/etc/pki/bao-client-key.pem";
};
# A homeserver on a hive with NO store identity at all — neither a local
# store nor a hand-placed leaf. The absence arm for the matrix-ctl cases below
# needs it, and defining it here rather than importing keeps each group's
# fixture set its own, as ./lib.nix asks.
inherit (shared) matrixNoBaoIdentity;
# 🩸 The hand-configured remote reader that named the hive's leaf and stopped:
# it holds the hive's own leaf, so it demonstrably reads the store, and is
# missing matrix-ctl's pair.
baoRemoteReaderMissingLeaves = hiveWithController {
deploy.matrix.enable = true;
deploy.bao.clientCertFile = "/etc/pki/bao-client.pem";
deploy.bao.clientKeyFile = "/etc/pki/bao-client-key.pem";
};
# The store's host with the hive's own reader pointed somewhere else.
matrixOnStoreHostReaderMoved = hiveWithController {
deploy.bao.enable = true;
deploy.matrix.enable = true;
deploy.bao.clientCertFile = "/etc/pki/h1.pem";
deploy.bao.clientKeyFile = "/etc/pki/h1-key.pem";
};
cases = [
{
# A hive holds no shared queue credential for its agents, wherever the
# store runs. The control: hive-c0re renders on both hosts.
name = "no hive renders a reader for a hive-shared agent queue credential";
ok = lib.all (m: !(m.systemd.services ? swarm-bao-queue-agent) && m.systemd.services ? hive-c0re) [
baoWithMatrix
baoRemoteReader
];
}
{
# hive-c0re runs as hive-core and the client key is `0600` root-owned
# inside a `0700` directory, so the identity reaches the daemon as a
# systemd credential and the environment names `%d` rather than the
# file. Both halves are asserted together because either alone is a
# daemon that fails at the TLS handshake, naming neither.
name = "a reader hands hive-c0re a store identity the daemon cannot open itself";
ok =
let
s = baoRemoteReader.systemd.services;
in
s ? hive-c0re
&& (s.hive-c0re.environment.BAO_CLIENT_CERT or null) == "%d/bao-client.pem"
&& (s.hive-c0re.environment.BAO_CLIENT_KEY or null) == "%d/bao-client-key.pem"
&& builtins.elem "bao-client.pem:/etc/pki/bao-client.pem" s.hive-c0re.serviceConfig.LoadCredential
&& builtins.elem "bao-client-key.pem:/etc/pki/bao-client-key.pem" s.hive-c0re.serviceConfig.LoadCredential;
}
{
# The CA is its own arm: absent means the system trust store, which is
# right for a deployment with a real CA and wrong for a self-signed one.
name = "a reader that names no store CA falls through to the system trust store";
ok =
let
s = baoRemoteReader.systemd.services;
in
s ? hive-c0re && !(s.hive-c0re.environment ? BAO_CACERT);
}
{
# Presence control for the arm above: the CA is conditional, not gone.
# Co-located, ./host-modules/glue-bao-tls.nix mints one and names it.
name = "a reader beside a self-signed store is given that store's CA";
ok =
let
s = baoWithMatrix.systemd.services;
in
s ? hive-c0re
&& (s.hive-c0re.environment.BAO_CACERT or null) == "%d/bao-ca.pem"
&& lib.any (c: lib.hasPrefix "bao-ca.pem:" c) s.hive-c0re.serviceConfig.LoadCredential;
}
{
# The same hole a third time, and the leaf whose absence is hardest to
# see from outside: it is consumed by a unit INSIDE a container, so a
# missing pairing renders as a container that comes up fine and publishes
# nothing.
name = "the store mints a leaf for matrix-ctl, and the container is pointed at it";
ok =
let
m = baoWithMatrix;
p = m.services.hyperhive.deploy.matrix;
in
lib.hasInfix "matrix-ctl.pem" m.systemd.services.swarm-bao-pki.script
&& p.ctlBaoClientCertFile == "/var/lib/swarm-bao-pki/matrix-ctl.pem"
&& p.ctlBaoClientKeyFile == "/var/lib/swarm-bao-pki/matrix-ctl-key.pem";
}
{
# The hive's own reader is not matrix-ctl's identity, and no path is
# derived from it: a homeserver host without the store that holds only
# the hive's reader gets the secret fetch that fails naming the options,
# like one holding nothing.
name = "a homeserver host without the store is refused when it holds only the hive's reader";
ok =
let
m = baoRemoteReaderMissingLeaves;
u = m.systemd.services.swarm-bao-matrix-oidc;
in
m.services.hyperhive.deploy.matrix.ctlBaoClientCertFile == null
&& !(u.environment ? BAO_CLIENT_CERT)
&& lib.hasInfix "deploy.matrix.ctlBaoClientCertFile" u.script
&& !(lib.hasInfix "bao login" u.script);
}
{
# The default is the file the minting unit writes, wherever the hive's
# own reader is pointed.
name = "on the store's host matrix-ctl's leaf is the one minted there, not one beside the hive's reader";
ok =
let
p = matrixOnStoreHostReaderMoved.services.hyperhive.deploy.matrix;
mint = matrixOnStoreHostReaderMoved.systemd.services.swarm-bao-pki.script;
in
p.ctlBaoClientCertFile == "/var/lib/swarm-bao-pki/matrix-ctl.pem"
&& p.ctlBaoClientKeyFile == "/var/lib/swarm-bao-pki/matrix-ctl-key.pem"
&& lib.hasInfix "[ -s /var/lib/swarm-bao-pki/matrix-ctl.pem ] ||" mint
&& lib.hasInfix "/var/lib/swarm-bao-pki matrix-ctl " mint;
}
{
# 🩸 The identity separation this whole arrangement buys, stated as the
# one thing that would silently undo it. The container gets MATRIX-CTL's
# leaf — whose grant is a single path — and not the hive's, which reads
# every agent credential in the swarm and this hive's own secrets. Both files exist in the same directory and
# both would evaluate, deploy and work.
name = "matrix-ctl presents its own leaf, never the hive's";
ok =
let
env =
baoWithMatrix.containers.hive-matrix.config.systemd.services.swarm-matrix-appservice-publish.environment;
hiveLeaf = baoWithMatrix.services.hyperhive.deploy.bao.clientCertFile;
in
env.BAO_CLIENT_CERT == "/var/lib/swarm-bao-pki/matrix-ctl.pem" && env.BAO_CLIENT_CERT != hiveLeaf;
}
{
# The bind mount is what makes the environment above resolvable: without
# 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.
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;
}
{
# The cert role ./host-modules/swarm-bao.nix writes, which is the one
# agreement the unit cannot get wrong, and the verb: a bare invocation
# exits non-zero with clap's usage, a deploy-time failure with no local
# signal.
name = "the publish unit is handed the store role and invokes its verb";
ok =
let
u = baoWithMatrix.containers.hive-matrix.config.systemd.services.swarm-matrix-appservice-publish;
in
u.environment.MATRIX_APPSERVICE_CERT_ROLE == "swarm-matrix-ctl"
&& lib.hasSuffix "/bin/swarm-matrix-ctl appservice publish" u.serviceConfig.ExecStart;
}
{
# 🩸 A secret is a path, never a value. Every variable the unit is given
# names a file or an address; the token itself is read out of the state
# dir at runtime, so nothing here can be a token and an environment block
# is world-readable through `systemctl show`.
name = "the publish unit's environment carries paths and addresses, never a token";
ok =
let
env =
baoWithMatrix.containers.hive-matrix.config.systemd.services.swarm-matrix-appservice-publish.environment;
in
!(lib.any (v: lib.hasInfix "as_token" v || lib.hasInfix "syt_" v) (lib.attrValues env));
}
{
# The absence arm, and the deployment it protects: a homeserver on a hive
# with no store identity at all. Without it the mount would name `null`
# as its source, which nixos renders as the literal string.
name = "a matrix container with no store identity binds no PKI";
ok = !(matrixNoBaoIdentity.containers.hive-matrix.bindMounts ? "/var/lib/swarm-bao-pki");
}
{
# 🩸 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 == [ "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 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
# 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);
}
];
in
runGroup "bao-matrix-reader" cases