From 626790a46450c8afa954e68230d2408ff49b0cc4 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 5 Oct 2026 20:47:24 +0200 Subject: [PATCH] hive CA: catch a deleted CA file however its path is spelled The CA-file assertion compared the option's raw string, so `/var/lib//swarm-ca/root.pem` or `/var/lib/hive-tls/./ca.pem` got past it. Both sides are normalised lexically now: empty and `.` components drop out, `..` removes the one before it, a trailing `/` goes. A symlink is not resolved at eval time, so a path through one still passes; docs/swarm/ca.md says so. --- docs/swarm/ca.md | 6 ++- nix/host-modules/host-ca-removed.nix | 24 ++++++++++- nix/module-eval/hive-tls.nix | 59 +++++++++++++++++++--------- 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/docs/swarm/ca.md b/docs/swarm/ca.md index 96a5f564..311e8eb0 100644 --- a/docs/swarm/ca.md +++ b/docs/swarm/ca.md @@ -182,8 +182,10 @@ under it. A host whose `deploy.bao.clientCaFile`, `deploy.bao.serverCaFile` or `deploy.swarm-controller.hiveClientCaFile` names one of those paths fails -evaluation. Those options name the store's mTLS CA, -`/var/lib/swarm-bao-pki/ca.pem` on the host that runs the store. +evaluation, however it's spelled with `//`, `.`, `..` or a trailing `/`. A +symlink to one isn't resolved, so it gets past the check. Those options name +the store's mTLS CA, `/var/lib/swarm-bao-pki/ca.pem` on the host that runs +the store. ## What to hand a peer diff --git a/nix/host-modules/host-ca-removed.nix b/nix/host-modules/host-ca-removed.nix index cad80b1f..50fcc97f 100644 --- a/nix/host-modules/host-ca-removed.nix +++ b/nix/host-modules/host-ca-removed.nix @@ -21,7 +21,29 @@ let "ca-previous.pem" "ca-key.pem" ]; - deleted = p: p == swarmCaDir || lib.hasPrefix "${swarmCaDir}/" p || lib.elem p hiveCaFiles; + # Lexical only: empty and `.` components drop out and `..` removes the + # one before it. A symlink is not resolved, so a path reaching a deleted + # file through one passes the check below. + normalise = + p: + lib.optionalString (lib.hasPrefix "/" p) "/" + + lib.concatStringsSep "/" ( + lib.foldl' ( + kept: c: + if c == "" || c == "." then + kept + else if c == ".." then + (if kept == [ ] then [ ] else lib.init kept) + else + kept ++ [ c ] + ) [ ] (lib.splitString "/" p) + ); + deleted = + p: + let + n = normalise p; + in + n == swarmCaDir || lib.hasPrefix "${swarmCaDir}/" n || lib.elem n (map normalise hiveCaFiles); # The hive CA's serial file and its adoption marker: neither is a CA, so # no CA-file option can name them, and nothing reads them. hiveCaLeftovers = map (f: "${deployCfg.hive-controller.tls.stateDir}/${f}") [ diff --git a/nix/module-eval/hive-tls.nix b/nix/module-eval/hive-tls.nix index e14caf46..d01c8cd4 100644 --- a/nix/module-eval/hive-tls.nix +++ b/nix/module-eval/hive-tls.nix @@ -79,6 +79,22 @@ let remainsAfterExit = u: u.serviceConfig.RemainAfterExit or false; + # Whether `m` fails evaluation on `deploy.