swarm-controller: hand the daemon the authority hives are issued from

Creating a hive's cert-auth role means writing the authority into the role
by value -- the store matches a presented certificate against the role's own
copy -- and nothing gave this daemon that file.

Named separately from deploy.bao.clientCaFile rather than read off it: that
option is the store's, saying which readers the store trusts on the host
that runs it, while a controller runs anywhere. The glue module supplies it
where the two are co-located, which is the same split baoClientCertFile
already makes against the hive reader's leaf.

Gated on the identity as well as the CA. Without a leaf there is nothing to
write a role with, so the file would reach a daemon that cannot act on it.

The module-eval arm needed a fixture of its own: a deployment that
self-signs both ends points clientCaFile and serverCaFile at one file, so on
the existing fixture the two authorities are the same string and wiring
either into the other's slot passes. controllerTwoCas is where they differ.
This commit is contained in:
atlas 2026-09-09 16:32:29 +02:00 committed by mara
commit 7f9e65e923
3 changed files with 93 additions and 1 deletions

View file

@ -36,6 +36,12 @@ in
services.hyperhive.deploy.swarm-controller = {
baoClientCertFile = lib.mkDefault "${pkiDir}/controller.pem";
baoClientKeyFile = lib.mkDefault "${pkiDir}/controller-key.pem";
}
// lib.optionalAttrs (baoDeploy.clientCaFile != null) {
# The authority the store already trusts hives by, handed to the daemon
# that has to write it into each hive's role. One file, two readers —
# a second copy would authenticate hives the store does not.
hiveClientCaFile = lib.mkDefault baoDeploy.clientCaFile;
};
};
}

View file

@ -32,6 +32,11 @@ let
deployCfg.swarm-controller.baoClientCertFile != null
&& deployCfg.swarm-controller.baoClientKeyFile != null;
# Creating a hive's role means writing to the store, so the CA alone is not
# enough — without an identity there is nothing to write it with, and the
# file would be handed to a daemon that cannot use it.
haveHiveClientCa = haveBaoIdentity && deployCfg.swarm-controller.hiveClientCaFile != null;
# `swarm_secret_client` reads these spellings explicitly rather than
# vaultrs's `VAULT_*` defaults — falling through to those builds a client
# with no identity and fails at the TLS handshake, naming neither. `%d` and
@ -47,6 +52,13 @@ let
# the self-signed one ./glue-bao-tls.nix mints, which is why that file
# names this path rather than leaving it to a default.
BAO_CACERT = "%d/bao-ca.pem";
}
// lib.optionalAttrs haveHiveClientCa {
# Not a `BAO_` name: that family is `swarm_secret_client`'s, and
# `BAO_CACERT` above is the *server* CA. This is the authority the
# store should trust hives by, which the controller reads as a value
# to put in each hive's cert-auth role.
SWARM_CONTROLLER_HIVE_CLIENT_CA_FILE = "%d/hive-client-ca.pem";
};
# What `swarmctl` needs in order to act on authelia from the host.
@ -529,6 +541,31 @@ in
'';
};
hiveClientCaFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/var/lib/swarm-ca/root.pem";
description = ''
Authority whose leaves are hive client certificates, so that this
daemon can create each hive's cert-auth role. The store matches a
presented certificate against the role's copy of this, which is why the
role carries the authority itself rather than a path to it.
The same material as {option}`services.hyperhive.deploy.bao.clientCaFile`,
named separately because that option is the **store's**: it says which
readers the store trusts, on the host that runs the store. A controller
runs anywhere, so it names its own copy, and a glue module supplies this
as a `mkDefault` where the two are co-located.
Public material a certificate authority, not a key so unlike every
other credential here it leaks nothing. It stays a path anyway, because
the file it names is the one the store already installs.
`null` leaves hive roles uncreated, which is the state a swarm is in
before anyone has onboarded a hive.
'';
};
queue = {
clientSecretFile = lib.mkOption {
type = lib.types.str;
@ -686,7 +723,8 @@ in
]
++ lib.optional (
haveBaoIdentity && deployCfg.bao.serverCaFile != null
) "bao-ca.pem:${deployCfg.bao.serverCaFile}";
) "bao-ca.pem:${deployCfg.bao.serverCaFile}"
++ lib.optional haveHiveClientCa "hive-client-ca.pem:${deployCfg.swarm-controller.hiveClientCaFile}";
# The placeholder default that makes the above non-fatal.
# `LoadCredential=` takes priority over `SetCredential=`, so this is