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.
This commit is contained in:
parent
69b70a9c6f
commit
dc418a5223
24 changed files with 3760 additions and 2997 deletions
216
nix/module-eval/bao-controller.nix
Normal file
216
nix/module-eval/bao-controller.nix
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
# `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
|
||||
Loading…
Reference in a new issue