# Glue: give the secret store a PKI of its own, and point it at it. # # ONE PAIRING PER FILE — `glue--.nix`. A single module holding # every co-location default becomes the file nobody dares change, because a # reader cannot tell which of its rules their deployment is subject to. Each # of these should be deletable on its own, and deleting this one leaves a # store that takes operator-provided certificates and nothing else. # # ⚠️ Why the PKI lives HERE and not in ./swarm-bao.nix: the store must have no # opinion about where its identity comes from. Minting is an opinion — the # most consequential one available — so it belongs to the glue that decides # this deployment self-signs, not to the service that merely serves what it is # handed. A deployment with a real internal CA drops this file and names its # own paths; nothing in the store changes. # # ⚠️ Not the hive CA and not the swarm CA. The store will eventually # distribute both, and an authority you must already hold a certificate from # cannot be one the store hands out — reach the store to get the CA material, # need a cert from that CA to reach the store. This CA signs exactly two # things and distributes nothing, so it cannot enter that cycle. # # ⚠️ Files like this are the only place a `deploy.` value may derive from # a `deploy..enable`. Everywhere else that is forbidden. The exception # earns itself: the derivation happens either way, and the alternative is # having it spread through the service modules where it is invisible. # # Everything is `mkDefault`. An operator naming their own paths wins. { pkgs, lib, config, ... }: let hyperhiveCfg = config.services.hyperhive; deployCfg = hyperhiveCfg.deploy; cfg = hyperhiveCfg.swarm.bao; # Host-side, outside the container's tree, for the same reason the raft data # is: `nixos-container destroy` must not take it. Losing the CA key means # re-issuing every client certificate in the swarm. pkiDir = "/var/lib/swarm-bao-pki"; # What a reader calls itself to the store. The hive's name, because a bao # cert-auth role matches on the CN — this is an interface, not a label. clientCn = if hyperhiveCfg.hiveName != null then hyperhiveCfg.hiveName else cfg.domain; # $1 dir $2 basename $3 CN $4 SAN or "" $5 EKU signLeaf = pkgs.writeShellScript "swarm-bao-sign-leaf" '' set -euo pipefail d="$1"; base="$2"; cn="$3"; sans="$4"; eku="$5" csr="$(mktemp "$d/$base.csr.XXXXXX")" ext="$(mktemp "$d/$base.ext.XXXXXX")" trap 'rm -f "$csr" "$ext"' EXIT openssl req -newkey rsa:4096 -nodes -sha256 \ -keyout "$d/$base-key.pem" -out "$csr" -subj "/CN=$cn" { [ -n "$sans" ] && printf 'subjectAltName=%s\n' "$sans" printf 'basicConstraints=critical,CA:FALSE\n' printf 'keyUsage=critical,digitalSignature,keyEncipherment\n' printf 'extendedKeyUsage=%s\n' "$eku" } > "$ext" openssl x509 -req -in "$csr" -CA "$d/ca.pem" -CAkey "$d/ca-key.pem" \ -CAcreateserial -days 3650 -sha256 -extfile "$ext" -out "$d/$base.pem" chmod 0600 "$d/$base-key.pem" chmod 0644 "$d/$base.pem" ''; in { config = lib.mkIf (hyperhiveCfg.enable && deployCfg.bao.enable) { services.hyperhive.deploy.bao = { serverCertFile = lib.mkDefault "${pkiDir}/server.pem"; serverKeyFile = lib.mkDefault "${pkiDir}/server-key.pem"; clientCaFile = lib.mkDefault "${pkiDir}/ca.pem"; }; # Idempotent on ABSENCE, never on content. Re-issuing the CA invalidates # every client certificate already trusting it, so a rebuild that # "refreshed" it would lock every reader in the swarm out at once — the # same rule the store's TPM PIN unit follows, for a sharper reason. systemd.services.swarm-bao-pki = { description = "mint the swarm secret store's own CA and leaves"; before = [ "swarm-bao-certs.service" ]; requiredBy = [ "swarm-bao-certs.service" ]; path = [ pkgs.openssl pkgs.coreutils ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; }; script = '' set -euo pipefail install -d -m 0700 ${pkiDir} if [ ! -s ${pkiDir}/ca.pem ]; then openssl req -x509 -newkey rsa:4096 -nodes -sha256 -days 3650 \ -keyout ${pkiDir}/ca-key.pem -out ${pkiDir}/ca.pem \ -subj "/CN=swarm-bao-ca ${cfg.domain}" \ -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \ -addext "keyUsage=critical,keyCertSign,cRLSign" chmod 0600 ${pkiDir}/ca-key.pem chmod 0644 ${pkiDir}/ca.pem fi # The store's own identity, and the identity of a reader on this host. # A reader elsewhere gets its leaf from this CA out of band — that is # what makes the store reachable from another machine at all, and why # the CA is a file rather than a service. [ -s ${pkiDir}/server.pem ] || ${signLeaf} ${pkiDir} server \ ${lib.escapeShellArg cfg.domain} ${lib.escapeShellArg "DNS:${cfg.domain}"} serverAuth [ -s ${pkiDir}/client.pem ] || ${signLeaf} ${pkiDir} client \ ${lib.escapeShellArg clientCn} "" clientAuth ''; }; }; }