# The swarm's root certificate in this agent's trust, fetched from the store # at boot by the agent itself. # # The root is the store's services `pki` mount's CA. It does not exist when the # agent is built, so the meta flake names no CA file: this unit reads it from # the store with the agent's own certificate (./bao.nix keeps it current) and # writes two files, both public: # # - `bundle`: the system CAs plus the swarm root. `SSL_CERT_FILE` replaces the # trust store of Rust (rustls platform verifier), Go and OpenSSL clients, so # it carries both halves. # - `root`: the swarm root alone, for `NODE_EXTRA_CA_CERTS`, which adds to # Node's store (Claude Code). # # A process reads its trust when it starts, so a rotated root reaches the # consumers below at their next start. They `Requires=` this unit, which keeps # one from running with a store that lacks the root. { pkgs, lib, config, ... }: let cfg = config.services.hyperhive.agent.bao; agentCfg = config.services.hyperhive.agent; agentName = agentCfg.user.name; # The server CA id ./bao.nix loads. serverCaCredential = "hive-agent-bao-server-ca"; unitName = "hive-agent-trust-bundle"; runtimeDir = unitName; bundleFile = "/run/${runtimeDir}/bundle.pem"; rootFile = "/run/${runtimeDir}/root.pem"; systemBundle = "/etc/ssl/certs/ca-certificates.crt"; configured = cfg.addr != null; storeRetry = import ../host-modules/lib/store-retry.nix { }; # Units that dial a swarm service over TLS. The first group is defined # whenever the agent module is; the rest exist under the same condition as # their definitions, because ordering on a unit that is not defined would # generate an inert one. consumers = [ "hive-agent" "hive-bash-daemon" "hive-subagent-daemon" "hive-mcp-http" "hive-forge-notify" ] ++ lib.optional (agentCfg.matrixAccounts != { }) "hive-matrix-daemon" ++ lib.optional (agentCfg.github.enable) "hive-github-notify" ++ lib.optional (agentCfg.icon != null && agentCfg.forge.url != null) "forge-avatar-sync"; trustEnv = { SSL_CERT_FILE = bundleFile; NODE_EXTRA_CA_CERTS = rootFile; }; in { options.services.hyperhive.agent.bao.servicesPkiMount = lib.mkOption { type = lib.types.str; default = "pki"; example = "pki"; description = '' Mount path of the store's services `pki` engine, whose root is the one anchor every swarm service certificate chains to. Set by the generated meta flake from the host's `services.hyperhive.deploy.bao.servicesPkiMountPath`. `${unitName}.service` reads `/ca/pem` from it. ''; }; config = lib.mkIf configured { # Every process, including a bash task's children, resolves trust through # these, as it resolves `HIVE_FORGE_TOKEN_FILE`. systemd.globalEnvironment = trustEnv; environment.variables = trustEnv; systemd.services = { ${unitName} = { description = "fetch the swarm root certificate from the secret store"; after = [ "network.target" "hive-agent-bao-identity.service" ]; wantedBy = [ "multi-user.target" ]; path = [ pkgs.coreutils pkgs.curl pkgs.openssl ]; inherit (storeRetry) startLimitBurst startLimitIntervalSec; serviceConfig = storeRetry.serviceConfig // { Type = "oneshot"; RemainAfterExit = false; TimeoutStartSec = 60; User = agentName; Group = agentName; RuntimeDirectory = runtimeDir; # Public certificates, read by every consumer's user. RuntimeDirectoryMode = "0755"; RuntimeDirectoryPreserve = "yes"; UMask = "0022"; LoadCredential = [ serverCaCredential ]; }; # Overrides the global value: this unit runs before the bundle exists, # and its own TLS is checked against the credential below or the # system bundle. environment = { SSL_CERT_FILE = lib.mkForce systemBundle; BAO_ADDR = cfg.addr; BAO_CLIENT_CERT = cfg.clientCertFile; BAO_CLIENT_KEY = cfg.clientKeyFile; }; script = '' set -euo pipefail for f in "$BAO_CLIENT_CERT" "$BAO_CLIENT_KEY"; do if [ ! -s "$f" ]; then echo "this agent has no store identity at $f, so it cannot fetch the swarm root certificate." >&2 exit 1 fi done cacert= if [ -s "$CREDENTIALS_DIRECTORY/${serverCaCredential}" ]; then cacert="$CREDENTIALS_DIRECTORY/${serverCaCredential}" echo "trusting bao via the delivered server CA credential" >&2 else echo "no bao server CA credential delivered; trusting the system bundle" >&2 fi dir=/run/${runtimeDir} err=$(mktemp -p "$dir" err.XXXXXX) raw=$(mktemp -p "$dir" raw.XXXXXX) root=$(mktemp -p "$dir" root.XXXXXX) bundle=$(mktemp -p "$dir" bundle.XXXXXX) trap 'rm -f "$err" "$raw" "$root" "$bundle"' EXIT # The listener demands a client certificate and the endpoint takes no # token, so no login precedes this. if ! curl --fail --silent --show-error \ --connect-timeout 10 --max-time 30 \ ''${cacert:+--cacert "$cacert"} \ --cert "$BAO_CLIENT_CERT" --key "$BAO_CLIENT_KEY" \ --output "$raw" \ ${lib.escapeShellArg "${cfg.addr}/v1/${cfg.servicesPkiMount}/ca/pem"} 2>"$err"; then echo "could not read the store's ${cfg.servicesPkiMount} root at $BAO_ADDR." >&2 cat "$err" >&2 exit 1 fi # Re-encoded rather than copied: it fails on a body that is not a # certificate and ends the file in the newline the concatenation # below relies on. if ! openssl x509 -in "$raw" -out "$root" 2>"$err"; then echo "the store's ${cfg.servicesPkiMount} root does not parse as a certificate." >&2 cat "$err" >&2 exit 1 fi cat ${systemBundle} "$root" >"$bundle" # Neither file moves unless it changed, so a reader never sees a half # write and the timer's rerun is silent. if cmp -s "$bundle" ${bundleFile} && cmp -s "$root" ${rootFile}; then echo "the store's ${cfg.servicesPkiMount} root is unchanged" exit 0 fi chmod 0644 "$root" "$bundle" mv -f "$root" ${rootFile} mv -f "$bundle" ${bundleFile} echo "wrote the swarm root certificate from the store's ${cfg.servicesPkiMount} mount" ''; }; } // lib.genAttrs consumers (_: { after = [ "${unitName}.service" ]; requires = [ "${unitName}.service" ]; }); systemd.timers.${unitName} = { description = "re-read the swarm root certificate from the secret store"; wantedBy = [ "timers.target" ]; timerConfig = { OnUnitInactiveSec = "1h"; RandomizedDelaySec = "1min"; }; }; }; }