Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/module-eval/builder.nix
atlas cafa120d77 swarm-builder: scope the forge token to the forge, one build at a time, opt-in
argus's review on #5177: the git credential helper answered every `get`,
so a config PR's own flake inputs could have git send the builder's forge
token to any host. The helper is now configured under
`credential.<forge-url>.helper` and itself answers only an https request
whose host is the forge's; a test runs it as git does and checks that
other hosts, http and non-get operations get nothing.

Builds are bounded: every `build_agent` node needs the one `builder` jobq
resource, so one runs at a time and the rest wait pending, and a newer
head cancels its PR's pending build (a running one finishes and writes on
its own commit). The builder also holds a one-permit semaphore around each
build.

`builder` and `review-classifier`, the forge logins swarm-controller owns,
join nix/reserved-names.nix, with a test tying the list to both constants.

The builder is now opt-in like the CI runner: `allSwarmServices` no longer
turns it on, since it runs unreviewed config PRs through the host's nix
daemon. mara had not answered atlas's question on the PR.
2026-10-14 03:30:46 +02:00

125 lines
4.8 KiB
Nix

# `checks.module-eval-builder` — see ./lib.nix for the shared rationale (why
# this suite exists, naming convention, "evaluates not executes"). The swarm
# builder's container, its default and its queue and IdP registrations; its
# store grants are in ./bao-grants.nix beside every other principal's.
{
pkgs,
lib,
self,
nixosSystem,
}:
let
inherit
(import ./lib.nix {
inherit
pkgs
lib
self
nixosSystem
;
})
hive
hiveWithController
runGroup
;
bare = hiveWithController { };
# On the store's host, which mints the builder's store identity.
withBuilder = hive {
deploy.bao.enable = true;
deploy.builder.enable = true;
};
servicesHere = hiveWithController { deploy.allSwarmServices = true; };
natsHere = hive {
swarm.nats.enable = true;
swarm.nats.authPackage = pkgs.emptyDirectory;
};
autheliaHere = hive {
deploy.bao.enable = true;
deploy.authelia.enable = true;
};
container = withBuilder.containers.swarm-builder;
inner = container.config;
svc = inner.systemd.services.swarm-builder;
env = svc.environment;
cases = [
{
name = "the swarm builder's container exists only where its host enables it";
ok = !(bare.containers ? swarm-builder) && withBuilder.containers ? swarm-builder;
}
{
# It runs unreviewed config PRs through the host's nix daemon, so hosting
# the shared services, even with the store's leaf here, does not turn it
# on. `nats` is the control that the switch is on at all.
name = "hosting the shared services leaves the builder off";
ok =
!servicesHere.services.hyperhive.deploy.builder.enable
&& !(servicesHere.containers ? swarm-builder)
&& servicesHere.services.hyperhive.deploy.bao.enable
&& servicesHere.services.hyperhive.deploy.nats.enable;
}
{
# A config PR is unreviewed input to an evaluation: the container keeps
# its own netns, and reaches the host's nix daemon through its socket
# alone, with no daemon of its own racing the bind.
name = "the builder's container has its own netns and builds only through the host's daemon socket";
ok =
container.privateNetwork
&& inner.services.hyperhive.swarmContainer.privateNetwork
&& container.bindMounts."/nix/var/nix/daemon-socket".hostPath == "/nix/var/nix/daemon-socket"
&& !inner.systemd.services.nix-daemon.enable
&& !inner.systemd.sockets.nix-daemon.enable
&& inner.systemd.globalEnvironment.NIX_REMOTE == "daemon";
}
{
# The daemon holds its store leaf, handed in by nspawn without a copy on
# disk; the forge token is read per build and reaches only `git`.
name = "the builder loads only its store leaf, runs as a dynamic user and carries no token in its unit";
ok =
lib.elem "builder-bao-key" svc.serviceConfig.LoadCredential
&& env.BAO_CLIENT_KEY == "/run/credentials/swarm-builder.service/builder-bao-key"
&& env.SWARM_BUILDER_OIDC_CLIENT_ID == "swarm-builder"
&& lib.elem "--load-credential=builder-bao-key:/var/lib/swarm-bao-pki/builder-key.pem" container.extraFlags
&& svc.serviceConfig.DynamicUser
&& !(lib.any (lib.hasSuffix "TOKEN") (lib.attrNames env));
}
{
# The check builds on the sources a hive on this host would render into
# its meta flake, so a green check means the same base.
name = "the builder builds on the hyperhive and nixpkgs this host's hives use";
ok =
env.SWARM_BUILDER_HYPERHIVE_FLAKE == withBuilder.services.hyperhive.c0re.hyperhiveFlake
&& env.SWARM_BUILDER_NIXPKGS_FLAKE == withBuilder.services.hyperhive.c0re.nixpkgsFlake
&& lib.hasSuffix "-check-flake" env.SWARM_BUILDER_CHECK_FLAKE
&& env.SWARM_BUILDER_CONFIG_ORG == "agent-configs"
&& env.SWARM_BUILDER_FORGE_URL == "https://${withBuilder.services.hyperhive.swarm.forge.domain}";
}
{
# The responder denies a client id it does not know, and a NATS denial
# arrives as a timeout.
name = "the queue's responder grants the builder's client its subject";
ok = lib.hasInfix "--builder-client swarm-builder" natsHere.containers.swarm-nats.config.systemd.services.swarm-nats-auth.serviceConfig.ExecStart;
}
{
name = "the builder's client is registered as a machine, and the publisher carries its secret";
ok =
let
c = lib.findFirst (
c: c.id == "swarm-builder"
) null autheliaHere.services.hyperhive.swarm.authelia.oidc.clients;
in
c != null
&& c.kind == "machine"
&& lib.hasInfix "secret/swarm/services/swarm-builder/oidc/client" (
autheliaHere.systemd.services.swarm-secret-publish.script
);
}
];
in
runGroup "builder" cases