# `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