# Removal shim for the CAs a host held outside the secret store: the # swarm root under `/var/lib/swarm-ca` (and its `services.hyperhive.swarm.ca.*` # options), and a hive CA's `ca.pem`, `ca-previous.pem`, `ca-key.pem`, # `ca.srl` and `.swarm-ca-adopted` in `deploy.hive-controller.tls.stateDir`. # # The secret store's `pki` root is the only trust anchor: it issues every # hive's gateway leaf and the swarm's service names, and its key never leaves # the store. Nothing generates, reads or bundles either CA, so a definition of # a removed option fails evaluation with a message, a CA-file option naming # one of the deleted paths fails it too, and every host deletes the files. # # Self-contained and deletable, like ./swarm-peers-removed.nix: one file to # remove when the deprecation window closes. { lib, config, ... }: let deployCfg = config.services.hyperhive.deploy; swarmCaDir = "/var/lib/swarm-ca"; hiveCaFiles = map (f: "${deployCfg.hive-controller.tls.stateDir}/${f}") [ "ca.pem" "ca-previous.pem" "ca-key.pem" ]; # 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}") [ "ca.srl" ".swarm-ca-adopted" ]; # Every option that names a CA the store's mTLS checks against. One # pointing at a deleted file fails the unit that reads it at start. caFileOptions = { "services.hyperhive.deploy.bao.clientCaFile" = deployCfg.bao.clientCaFile; "services.hyperhive.deploy.bao.serverCaFile" = deployCfg.bao.serverCaFile; "services.hyperhive.deploy.swarm-controller.hiveClientCaFile" = deployCfg.swarm-controller.hiveClientCaFile; }; removed = name: lib.mkRemovedOptionModule [ "services" "hyperhive" "swarm" "ca" name ] '' The host-held swarm root CA is gone: the secret store's pki root is the only trust anchor, and nothing generates or reads a root under ${swarmCaDir}. Remove this definition. See docs/swarm/ca.md. ''; in { imports = map removed [ "autoConfigure" "stateDir" "validityDays" ]; assertions = lib.mapAttrsToList (option: path: { assertion = path == null || !(deleted path); message = '' ${option} is ${toString path}, a file every host deletes along with the host-held CA it belonged to. Point it at the store's mTLS CA: /var/lib/swarm-bao-pki/ca.pem on the host that runs the store, the copy placed by hand on any other host. See docs/swarm/ca.md. ''; }) caFileOptions; # Both CAs' private keys are secrets outside the store, and the # certificates beside them are anchors nothing issues under. The swarm # root at the option's old default path only: a relocated `stateDir` is # the operator's to delete. systemd.tmpfiles.rules = [ "R ${swarmCaDir}" ] ++ map (f: "r ${f}") (hiveCaFiles ++ hiveCaLeftovers); }