hyperhive/nix/module-eval/bao-controller.nix
müde dc418a5223 nix: split module-eval into per-subsystem checks
The single module-eval derivation forced ~62 full nixosSystem
fixtures live at once to compute its cases list: 10.6GB peak RSS /
5m25s to evaluate, by far the dominant cost in nix flake check.
Splits it into 21 independent checks.module-eval-* derivations
(1-7 fixtures each) sharing builders/helpers via module-eval/lib.nix,
so no single derivation needs more than a handful of fixtures live
at once. A few cases spanning two clusters carry a small duplicated
fixture rather than threading shared state through lib.nix.
2026-09-20 04:25:54 +02:00

216 lines
9.8 KiB
Nix

# `checks.module-eval-bao-controller` — see ./lib.nix for the shared
# rationale (why this suite exists, naming convention, "evaluates
# not executes").
{
pkgs,
lib,
self,
nixosSystem,
}:
let
inherit
(import ./lib.nix {
inherit
pkgs
lib
self
nixosSystem
;
})
hive
runGroup
;
# Store and controller on one machine, with a CN no default could supply.
# The odd value is what lets the case below tell "both ends read the same
# option" from "both ends happen to say swarm-controller".
baoControllerHere = hive {
deploy.bao.enable = true;
deploy.bao.bootstrapTokenFile = "/run/secrets/bao-bootstrap.token";
deploy.bao.controllerCommonName = "cn-marker-not-a-default";
deploy.swarm-controller.enable = true;
};
# The controller with no store, which is every spread deployment. Nothing
# mints here, so the pairing must leave the paths unset rather than name
# files this host will never have.
controllerNoStore = hive { deploy.swarm-controller.enable = true; };
# The two authorities told apart. A deployment that self-signs both ends
# points `clientCaFile` and `serverCaFile` at one file, so on the fixture
# above the CA a hive is issued from and the CA the store is verified by are
# the same string — and a case wiring either into the other's slot passes.
# This is the deployment where they differ, which is what makes the arm
# below able to fail at all.
controllerTwoCas = hive {
deploy.bao.enable = true;
deploy.bao.bootstrapTokenFile = "/run/secrets/bao-bootstrap.token";
deploy.swarm-controller.enable = true;
deploy.bao.clientCaFile = lib.mkForce "/etc/pki/hive-clients-ca.pem";
deploy.bao.serverCaFile = lib.mkForce "/etc/pki/store-server-ca.pem";
};
# The host's `bao` wrapper, pulled apart once so each case below names one
# property instead of a conjunction — a failing conjunction says only that
# something is wrong.
baoHostPackages = controllerTwoCas.environment.systemPackages;
baoWrapper = lib.findFirst (p: (p.name or "") == "bao-hive") null baoHostPackages;
baoWrapperCmd = if baoWrapper == null then "" else (baoWrapper.buildCommand or "");
cases = [
{
# Nothing asserted the PKI script before this, so a third leaf could be
# added to it and every case still passed — measured, not assumed: the
# commit that added one left `module-eval`'s derivation unchanged.
name = "the store mints a leaf for the controller, and the controller is pointed at it";
ok =
let
m = baoControllerHere;
pki = m.systemd.services.swarm-bao-pki.script;
in
lib.hasInfix "controller.pem" pki
&&
m.services.hyperhive.deploy.swarm-controller.baoClientCertFile
== "/var/lib/swarm-bao-pki/controller.pem"
&&
m.services.hyperhive.deploy.swarm-controller.baoClientKeyFile
== "/var/lib/swarm-bao-pki/controller-key.pem";
}
{
# What makes the one above mean something: a controller with no store
# has nothing to be pointed at. Naming a path here would be a file this
# host never gets, which fails at a TLS handshake rather than at eval.
name = "a controller on a host with no store is left without certificate paths";
ok =
let
c = controllerNoStore.services.hyperhive.deploy.swarm-controller;
in
c.baoClientCertFile == null && c.baoClientKeyFile == null;
}
{
# Being *pointed at* a leaf and *being handed* one are different claims,
# and the options above were the first without the second — declared,
# defaulted, and read by nothing. This is the arm that makes them reach
# the process.
#
# ⚠️ The LoadCredential source is asserted, not just the `%d` name: the
# controller's leaf and the hive reader's are two identities with two
# policies, and wiring `deploy.bao.clientCertFile` here would satisfy
# every `%d`-only check while giving the daemon a policy that cannot
# write an agent's credential.
name = "the controller is handed its own store leaf, not the hive reader's";
ok =
let
s = baoControllerHere.systemd.services;
in
s ? swarm-controller
&& (s.swarm-controller.environment ? BAO_ADDR)
&& (s.swarm-controller.environment.BAO_CLIENT_CERT or null) == "%d/bao-client.pem"
&& (s.swarm-controller.environment.BAO_CLIENT_KEY or null) == "%d/bao-client-key.pem"
&& builtins.elem "bao-client.pem:/var/lib/swarm-bao-pki/controller.pem" s.swarm-controller.serviceConfig.LoadCredential
&& builtins.elem "bao-client-key.pem:/var/lib/swarm-bao-pki/controller-key.pem" s.swarm-controller.serviceConfig.LoadCredential;
}
{
# A hive's cert-auth role carries the authority by value, so the daemon
# has to be handed the file rather than a path into the store's own
# directory it cannot read.
#
# ⚠️ The LoadCredential source is asserted, not just the `%d` name, for
# the reason the arm above gives — and here the wrong file is a
# *plausible* one: `deploy.bao.serverCaFile` is the CA a reader checks
# the store's certificate with, evaluates fine in this slot, and would
# make every hive role trust the wrong authority.
name = "the controller is handed the CA hives are issued from";
ok =
let
s = controllerTwoCas.systemd.services;
m = controllerTwoCas.services.hyperhive;
in
(s.swarm-controller.environment.SWARM_CONTROLLER_HIVE_CLIENT_CA_FILE or null)
== "%d/hive-client-ca.pem"
&& builtins.elem "hive-client-ca.pem:/etc/pki/hive-clients-ca.pem" s.swarm-controller.serviceConfig.LoadCredential
&& !builtins.elem "hive-client-ca.pem:/etc/pki/store-server-ca.pem" s.swarm-controller.serviceConfig.LoadCredential
&& m.deploy.swarm-controller.hiveClientCaFile == m.deploy.bao.clientCaFile;
}
{
# Same two-CA fixture, for the same reason: the wrapper verifies the
# STORE, so it takes `serverCaFile`. On a self-signing deployment both
# options name one file and either would pass; here the client CA in that
# slot is a case this arm fails.
#
# ⚠️ The package itself stays off `PATH` — `wrapProgram` renames the real
# binary, so an unwrapped `bao` is unreachable rather than merely
# discouraged. Operator's instruction, and the last assertion is what
# keeps a later "install the package too" from quietly undoing it.
name = "the host gets a wrapped bao CLI";
ok = baoWrapper != null;
}
{
name = "the wrapped bao CLI carries this store's address";
ok = lib.hasInfix "--set-default BAO_ADDR" baoWrapperCmd;
}
{
# `serverCaFile` and not `clientCaFile`: the wrapper verifies the STORE.
# On a self-signing deployment both options name one file and either
# would pass, which is why this uses the two-CA fixture.
#
# ⚠️ The flag and its VALUE together, escaped the same way the module
# escapes it: `BAO_CACERT` present and `store-server-ca.pem` present
# somewhere are two facts that do not add up to "the CA is set to that
# file", and a weaker pair of `hasInfix`es passes on a wrapper that sets
# neither to the other.
name = "the wrapped bao CLI verifies the store with the server CA";
ok =
lib.hasInfix "--set-default BAO_CACERT ${lib.escapeShellArg "/etc/pki/store-server-ca.pem"}" baoWrapperCmd
&& !lib.hasInfix "hive-clients-ca.pem" baoWrapperCmd;
}
{
# `wrapProgram` renames the real binary, so an unwrapped `bao` is
# unreachable rather than merely discouraged — operator's instruction.
# This is what keeps a later "install the package too" from undoing it.
name = "the unwrapped bao package stays off the host PATH";
ok = !builtins.elem controllerTwoCas.services.hyperhive.deploy.bao.package baoHostPackages;
}
{
# Absence arm for the one above: without a store identity there is
# nothing to write a role with, so handing over the authority would be
# giving a file to a daemon that cannot act on it.
name = "a controller with no store leaf is given no hive CA either";
ok =
let
s = controllerNoStore.systemd.services;
in
!(s.swarm-controller.environment ? SWARM_CONTROLLER_HIVE_CLIENT_CA_FILE)
&& !(lib.any (c: lib.hasPrefix "hive-client-ca" c) s.swarm-controller.serviceConfig.LoadCredential);
}
{
# Absence arm for the one above, and what makes it mean anything: a
# controller with no leaf gets no store environment at all rather than
# variables naming files this host never receives.
name = "a controller with no store leaf is given no store environment";
ok =
let
s = controllerNoStore.systemd.services;
in
s ? swarm-controller
&& !(s.swarm-controller.environment ? BAO_ADDR)
&& !(lib.any (c: lib.hasPrefix "bao-" c) s.swarm-controller.serviceConfig.LoadCredential);
}
{
# The CN is an interface between two files: the store writes a role that
# matches it, the PKI mints a leaf that carries it. They read one option,
# and this is what says so — the fixture's value cannot come from a
# default, so matching it in both places is not a coincidence.
name = "the cert-auth role and the minted leaf take their subject from one option";
ok =
let
m = baoControllerHere;
role = m.systemd.services.swarm-bao-controller-policy.script;
pki = m.systemd.services.swarm-bao-pki.script;
in
lib.hasInfix "cn-marker-not-a-default" role && lib.hasInfix "cn-marker-not-a-default" pki;
}
];
in
runGroup "bao-controller" cases