# `checks.module-eval-agent-forge-bao` — see ./lib.nix for the shared # rationale (why this suite exists, naming convention, "evaluates # not executes"). # # The agent side of the swarm-minted forge token: ../agent-modules/forge-token.nix # fetches it, and ../agent-modules/forge.nix's readers find it. { pkgs, lib, self, nixosSystem, }: let inherit (import ./lib.nix { inherit pkgs lib self nixosSystem ; }) agentWith runGroup ; forgeUrl = "http://forge.t.local"; baoAddr = "https://bao.t.local:8200"; # A forge and a store: the fetch exists and every reader points at it. agentForgeBao = agentWith { services.hyperhive.agent.bao.addr = baoAddr; services.hyperhive.agent.forge.url = forgeUrl; services.hyperhive.agent.icon = pkgs.emptyFile; }; # A forge and no store: the absence arm, and what makes the cases above able # to fail. agentForgeNoBao = agentWith { services.hyperhive.agent.forge.url = forgeUrl; services.hyperhive.agent.icon = pkgs.emptyFile; }; fetchUnit = machine: machine.systemd.services.hive-agent-forge-token; tokenFile = machine: machine.services.hyperhive.agent.forge.tokenFile; in let cases = [ { # The whole switch is the store address, as for the queue credential. name = "an agent with a store address fetches its forge token"; ok = agentForgeBao.systemd.services ? hive-agent-forge-token && agentForgeBao.systemd.timers ? hive-agent-forge-token; } { name = "an agent told no store address fetches no forge token"; ok = !(agentForgeNoBao.systemd.services ? hive-agent-forge-token) && !(agentForgeNoBao.systemd.timers ? hive-agent-forge-token) && !(agentForgeNoBao.systemd.globalEnvironment ? HIVE_FORGE_TOKEN_FILE); } { # The nix half of `swarm_secret_client::forge::agent_token_path` plus the # mount. Spelled out: the Rust test pins `swarm/agents/atlas/forge-token`, # and a drift between the two is a fetch that 404s forever and says # "not minted yet". name = "the fetch reads the agent's own forge-token path"; ok = let name = agentForgeBao.services.hyperhive.agent.user.name; in lib.hasInfix "secret/swarm/agents/${name}/forge-token" (fetchUnit agentForgeBao).script; } { # One store identity per agent: the fetch presents the same certificate # the identity check proves. Every `BAO_*` value is an address or a # `%d/` path, never a value. name = "the fetch presents the agent's own store identity, by path"; ok = let u = fetchUnit agentForgeBao; e = u.environment; in builtins.elem "hive-agent-bao-cert" u.serviceConfig.LoadCredential && builtins.elem "hive-agent-bao-key" u.serviceConfig.LoadCredential && e.BAO_ADDR == baoAddr && e.BAO_CLIENT_CERT == "%d/hive-agent-bao-cert" && e.BAO_CLIENT_KEY == "%d/hive-agent-bao-key"; } { # The field is the secret. It goes to a file by redirect and is never # echoed. name = "the fetch writes the token by redirect and never echoes it"; ok = let s = (fetchUnit agentForgeBao).script; in lib.hasInfix "-field=value" s && lib.hasInfix "> ${lib.escapeShellArg "/run/hive-agent-forge-token/token.new"}" s && !(lib.hasInfix "echo \"$(bao" s) && !(lib.hasInfix "echo $(bao" s); } { # A timer can only start an inactive unit, so the fetch must not stay # active; the directory, and the token in it, has to outlive each run. name = "the fetch can be re-run by its timer without losing the token"; ok = let c = (fetchUnit agentForgeBao).serviceConfig; in !c.RemainAfterExit && c.RuntimeDirectory == "hive-agent-forge-token" && c.RuntimeDirectoryPreserve == "yes" && c.UMask == "0377"; } { # Rename in only on a change: a reader never sees half a token, and the # avatar watcher does not fire on every timer tick. name = "the fetch swaps the token in by rename, only when it changed"; ok = let s = (fetchUnit agentForgeBao).script; in lib.hasInfix "cmp -s" s && lib.hasInfix "mv -f" s; } { # Every unit and the bash-task runner find the token through this. A # path, never the value. name = "the fetched token's path is published to every unit"; ok = agentForgeBao.systemd.globalEnvironment.HIVE_FORGE_TOKEN_FILE == tokenFile agentForgeBao && tokenFile agentForgeBao == "/run/hive-agent-forge-token/token"; } { # The avatar sync has to re-fire when the swarm's token lands, which is # the file the fetch writes, not the state-dir file nothing writes any # more. name = "the avatar watcher follows the fetched token"; ok = agentForgeBao.systemd.paths.forge-avatar-sync.pathConfig.PathChanged == tokenFile agentForgeBao && builtins.elem "hive-agent-forge-token.service" agentForgeBao.systemd.services.forge-avatar-sync.after; } { # Both readers take the fetched token first and fall back to the state # file, which is still the only copy for an agent with no store identity. name = "the avatar sync reads the fetched token before the state file"; ok = let s = agentForgeBao.systemd.services.forge-avatar-sync.script; name = agentForgeBao.services.hyperhive.agent.user.name; fetched = lib.escapeShellArg (tokenFile agentForgeBao); state = lib.escapeShellArg "/agents/${name}/state/forge-token"; in lib.hasInfix "for f in ${fetched} ${state}; do" s; } { # tea-login copied the token into ~/.config/tea/config.yml, which # docs/swarm/credentials.md forbids for a store secret. Gone, with the # package it configured. name = "no tea-login unit and no tea package"; ok = !(agentForgeBao.systemd.services ? tea-login) && !(agentForgeNoBao.systemd.services ? tea-login) && !(builtins.elem pkgs.tea agentForgeBao.environment.systemPackages); } ]; in runGroup "agent-forge-bao" cases