From aa784a746d24e8582bfb52c06db2a7c9e4958060 Mon Sep 17 00:00:00 2001 From: atlas Date: Sun, 30 Aug 2026 18:54:36 +0200 Subject: [PATCH] module-eval: the seal, the store's identity, and the reader pairing The seal choice gates the TPM machinery, in both directions. The store's three certificate paths are a function of the glue: present when the store is deployed, and an operator's own path beats the mkDefault. The absence arm this replaces asserted the store defaults NO path -- true while nothing supplied one, false the moment something did. The reader unit is a function of the PAIRING: present with store and homeserver together, absent with the store alone. That pair is what makes it glue rather than a feature of either side, and it is the case that fails if a later change wires the reader to one of them. --- nix/module-eval.nix | 71 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/nix/module-eval.nix b/nix/module-eval.nix index b07b80da..67b8b5e5 100644 --- a/nix/module-eval.nix +++ b/nix/module-eval.nix @@ -71,6 +71,26 @@ let bare = hive { }; withCi = hive { deploy.forgejo.ci.enable = true; }; + baoPkcs11 = hive { + deploy.bao.enable = true; + deploy.bao.seal = "pkcs11"; + }; + baoShamir = hive { + deploy.bao.enable = true; + deploy.bao.seal = "shamir"; + }; + baoExplicitCerts = hive { + deploy.bao.enable = true; + deploy.bao.serverCertFile = "/etc/pki/bao.pem"; + deploy.bao.serverKeyFile = "/etc/pki/bao-key.pem"; + }; + # 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. + baoWithMatrix = hive { + deploy.bao.enable = true; + deploy.matrix.enable = true; + }; + # A priority collision is a property of the *option*, not # of the merged value's interior — nix throws the moment the value is # demanded at all, so `seq`-ing each `serviceConfig` value to WHNF is @@ -134,6 +154,57 @@ let name = "the CI container's unit definitions merge without a priority collision"; ok = forceCiServiceConfigs; } + { + # The store's seal is spread over five gates — the stanza, the + # provisioning unit, a bind mount, a device and an EnvironmentFile. + # Rendering only some of them is the dangerous state: a store that + # says hardware-backed and seals with a software key, which no + # assertion can catch because every value is individually valid. + name = "a shamir store renders no TPM provisioning unit"; + ok = !(baoShamir.systemd.services ? swarm-bao-token); + } + { + # Presence control for the case above. Without it, a typo in the + # option name would satisfy the absence arm forever. + name = "a pkcs11 store renders the TPM provisioning unit"; + ok = baoPkcs11.systemd.services ? swarm-bao-token; + } + { + # The store's mTLS identity is a separate trust domain from both CAs in + # this tree, because it must not come from an authority the store will + # itself distribute. What supplies it is the glue, which mints a CA of + # the store's own — so an enabled store has all three paths, and if this + # ever reads null again the store stops coming up on its own. + name = "a deployed store is given its own certificate, key and client CA"; + ok = + let + b = baoPkcs11.services.hyperhive.deploy.bao; + in + b.serverCertFile != null && b.serverKeyFile != null && b.clientCaFile != null; + } + { + # Everything the glue sets is `mkDefault`, and this is the case that + # says so: a deployment whose certificates come from somewhere the glue + # has never heard of must win. Also the presence control for the case + # above — a renamed option would read `null` on both and satisfy + # neither, but only this one names a value. + name = "an operator's own certificate path beats the glue's default"; + ok = baoExplicitCerts.services.hyperhive.deploy.bao.serverCertFile == "/etc/pki/bao.pem"; + } + { + # 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 registration token"; + ok = baoWithMatrix.systemd.services ? swarm-bao-matrix-token; + } + { + # Absence arm. A store with nothing to serve renders no reader, so the + # unit is a function of the PAIRING rather than of the store — which is + # the property that makes it glue instead of a feature of either side. + name = "a store with no homeserver beside it renders no token reader"; + ok = !(baoPkcs11.systemd.services ? swarm-bao-matrix-token); + } ]; bad = builtins.filter (c: !c.ok) cases;