Watch
0
0
Fork
You've already forked hyperhive
0

hive CA: delete ca.pem, ca-previous.pem and ca-key.pem, the bundle is the store's root

The gateway leaf comes from the store's pki mount, so a hive CA's
certificates anchor nothing and its key is a secret outside the store.

- hive-tls-ca writes trust-bundle.pem from swarm-services-root.pem alone;
  the ca.pem and ca-previous.pem slots are gone.
- Every host deletes ca.pem, ca-previous.pem and ca-key.pem in
  deploy.hive-controller.tls.stateDir with tmpfiles `r` rules that follow
  the option. The removal shim, now host-ca-removed.nix, holds these
  beside the /var/lib/swarm-ca rule and the swarm.ca.* removals.
- deploy.bao.clientCaFile, deploy.bao.serverCaFile and
  deploy.swarm-controller.hiveClientCaFile naming /var/lib/swarm-ca or one
  of the deleted hive CA files fail evaluation, naming the store's mTLS CA
  (/var/lib/swarm-bao-pki/ca.pem on the store's host) as the replacement.

module-eval hive-tls covers the single-anchor bundle, the deletion (incl.
a relocated stateDir) and the assertion on all three options.

Refs #4588
This commit is contained in:
atlas 2026-10-05 19:54:55 +02:00 • committed by mara
commit 4e5a642928
10 changed files with 170 additions and 122 deletions

View file

@ -155,7 +155,7 @@ let
&& lib.hasInfix ''"$BAO_ADDR/v1/pki/ca/pem"'' noSvcFetch
&& lib.hasInfix ''mv -f "$svcroot.new" "$svcroot"'' noSvcFetch
&& lib.hasInfix ''svcroot="$d/swarm-services-root.pem"'' noService.systemd.services.hive-tls-ca.script
&& lib.hasInfix ''if [ -s "$svcroot" ]; then anchors+=("$svcroot"); fi'' noService.systemd.services.hive-tls-ca.script;
&& lib.hasInfix ''cat "$svcroot" > "$bundle"'' noService.systemd.services.hive-tls-ca.script;
}
{
# The hive's own identity, not its reader identity: the reader's policy
@ -258,18 +258,12 @@ let
&& mentions noService "swarm-services-root.pem";
}
{
# Three slots and no fourth: a root slot beside them is an anchor
# nothing issues under.
name = "the trust bundle anchors on the store's root and a hive CA's public certificates only";
# One anchor: any other slot is a root nothing issues under.
name = "the trust bundle is the store's root alone";
ok =
let
slots = lib.filter (l: lib.hasPrefix "if [ -s " l && lib.hasInfix "anchors+=(" l) (
lib.splitString "\n" caUnit.script
);
in
lib.length slots == 3
&& lib.hasInfix ''if [ -s "$svcroot" ]; then anchors+=("$svcroot"); fi'' caUnit.script
&& !(lib.hasInfix ''anchors+=("$root")'' caUnit.script);
lib.hasInfix ''cat "$svcroot" > "$bundle"'' caUnit.script
&& !(lib.hasInfix "anchors+=(" caUnit.script)
&& !(lib.hasInfix "ca.pem" caUnit.script);
}
{
# The control is the all-local host, which defines none of them.
@ -322,17 +316,74 @@ let
&& !(lib.hasInfix refusal (imp noService));
}
{
# Nothing reads or removes a `ca-key.pem`, so a rollback finds the
# key and does not mint a new anchor. The control is the bundle
# reading `ca.pem` in the same script.
name = "a hive CA key is neither read nor removed, and its public certificate stays in the trust bundle";
# The control is the same scan finding the store root's file, and a
# relocated state dir, whose rules follow it.
name = "no hive TLS unit reads a hive CA's files, and every host deletes them";
ok =
!(lib.hasInfix "ca-key.pem" caUnit.script)
&& !(lib.hasInfix "ca-key.pem" leafUnit.script)
&& lib.hasInfix ''ca="$d/ca.pem"'' caUnit.script
&& lib.hasInfix ''if [ -s "$ca" ]; then anchors+=("$ca"); fi'' caUnit.script
&& lib.hasInfix ''if [ -s "$prev" ]; then anchors+=("$prev"); fi'' caUnit.script
&& lib.hasInfix ''cat "''${anchors[@]}" > "$bundle"'' caUnit.script;
let
texts =
m:
map (u: m.systemd.services.${u}.script) [
"hive-tls-ca"
"hive-tls-resign"
"hive-gateway-self-signed-cert"
];
reads = m: needle: lib.any (lib.hasInfix needle) (texts m);
deletes =
m: dir:
lib.all (f: lib.elem "r ${dir}/${f}" m.systemd.tmpfiles.rules) [
"ca.pem"
"ca-previous.pem"
"ca-key.pem"
];
relocated = hiveWithController { deploy.hive-controller.tls.stateDir = "/srv/hive-tls"; };
in
!(lib.any (reads allLocal) [
"/ca.pem"
"ca-previous.pem"
"ca-key.pem"
])
&& reads allLocal "swarm-services-root.pem"
&& deletes allLocal "/var/lib/hive-tls"
&& deletes noService "/var/lib/hive-tls"
&& deletes relocated "/srv/hive-tls"
&& !(deletes relocated "/var/lib/hive-tls");
}
{
# The store host pointing its client CA at the old root: the store's and
# the controller's options both fire, the second through the glue's
# `mkDefault`. The control is the same host on its defaults.
name = "a CA-file option naming a deleted CA file fails evaluation, naming the store's mTLS CA";
ok =
let
refused =
m: option:
lib.any (
a:
!a.assertion
&& lib.hasInfix "services.hyperhive.deploy.${option} is" a.message
&& lib.hasInfix "/var/lib/swarm-bao-pki/ca.pem" a.message
) m.assertions;
oldRoot = hiveWithController {
deploy.singleHostSwarm = true;
deploy.bao.clientCaFile = "/var/lib/swarm-ca/root.pem";
};
hiveCa = hive {
deploy.bao.serverCaFile = "/var/lib/hive-tls/ca.pem";
deploy.swarm-controller.hiveClientCaFile = "/var/lib/hive-tls/ca-previous.pem";
};
options = [
"bao.clientCaFile"
"bao.serverCaFile"
"swarm-controller.hiveClientCaFile"
];
in
refused oldRoot "bao.clientCaFile"
&& refused oldRoot "swarm-controller.hiveClientCaFile"
&& refused hiveCa "bao.serverCaFile"
&& refused hiveCa "swarm-controller.hiveClientCaFile"
&& !(lib.any (refused allLocal) options)
&& allLocal.services.hyperhive.deploy.bao.clientCaFile == "/var/lib/swarm-bao-pki/ca.pem";
}
{
# The store host's hive presents the leaf ./glue-bao-tls.nix mints for