Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/module-eval/agent-github-bao.nix
atlas c6717bccb0 agents: re-pull the agent's own bao mTLS leaf on a timer
swarm-controller re-issues an agent's store leaf at half its validity, but
the running container kept presenting the copy its hive delivered at
start, so a re-issued leaf reached the agent only on its next restart.

hive-agent-bao-identity now seeds a live copy of the pair in its own
runtime directory from the delivered credentials, and re-runs every 10
minutes: it logs in with the live leaf, reads its own
swarm/agents/<agent>/bao-mtls (a read render_agent already grants), and
when the leaf changed, logs in with the new pair and swaps it in only if
the store accepts it.

Every unit in the container that logs in to the store presents the live
pair: hive-agent (queue-identity.nix), hive-matrix-daemon,
hive-subagent-daemon, hive-agent-forge-token, hive-agent-forge-accounts
and hive-agent-github-token. They keep loading the server CA as a
credential. The long-running daemons are ordered after the refresher so
the live pair exists when they start.

It renews only while the held leaf is still valid: the login that reads
the new leaf is made with the current one.

Refs #5017
2026-10-11 10:00:43 +02:00

112 lines
3.7 KiB
Nix

# `checks.module-eval-agent-github-bao` — see ./lib.nix for the shared
# rationale (why this suite exists, naming convention, "evaluates
# not executes").
#
# The agent side of the swarm-stored GitHub token: ../agent-modules/github-token.nix
# fetches it into the file ../agent-modules/github.nix's readers use.
{
pkgs,
lib,
self,
nixosSystem,
}:
let
inherit
(import ./lib.nix {
inherit
pkgs
lib
self
nixosSystem
;
})
agentWith
runGroup
;
baoAddr = "https://bao.t.local:8200";
# A store, and the integration on by default.
agentGithubBao = agentWith {
services.hyperhive.agent.bao.addr = baoAddr;
};
# No store: the absence arm, and what makes the cases above able to fail.
agentGithubNoBao = agentWith { };
# A store, and the integration switched off.
agentGithubOff = agentWith {
services.hyperhive.agent.bao.addr = baoAddr;
services.hyperhive.agent.github.enable = false;
};
fetchUnit = machine: machine.systemd.services.hive-agent-github-token;
has = machine: machine.systemd.services ? hive-agent-github-token;
hasTimer = machine: machine.systemd.timers ? hive-agent-github-token;
in
let
cases = [
{
name = "an agent with a store address and the integration on fetches its github token";
ok = has agentGithubBao && hasTimer agentGithubBao;
}
{
name = "an agent with no store address, or with the integration off, fetches none";
ok =
!(has agentGithubNoBao)
&& !(hasTimer agentGithubNoBao)
&& !(has agentGithubOff)
&& !(hasTimer agentGithubOff);
}
{
# The nix half of `swarm_secret_client::github::account_path`, and the
# file ./github.nix's `gh` wrapper, credential helper and poller read.
name = "the fetch reads the agent's own github-token path into its state-dir file";
ok =
let
name = agentGithubBao.services.hyperhive.agent.user.name;
s = (fetchUnit agentGithubBao).script;
in
lib.hasInfix "bao kv get -format=json secret/swarm/agents/${name}/github-token >" s
&& lib.hasInfix "token=/agents/${name}/state/github-token" s
&& lib.hasInfix ".data.data.value | strings" s;
}
{
# The agent user owns its state dir (./user.nix), and the file keeps
# its `0600` mode.
name = "the fetch runs as the agent, with its own store identity";
ok =
let
u = fetchUnit agentGithubBao;
name = agentGithubBao.services.hyperhive.agent.user.name;
in
u.serviceConfig.User == name
&& u.serviceConfig.UMask == "0077"
&& u.environment.BAO_ADDR == baoAddr
&& u.environment.BAO_CLIENT_CERT == "/run/hive-agent-bao-identity/cert"
&& u.environment.BAO_CLIENT_KEY == "/run/hive-agent-bao-identity/key";
}
{
# A file a hive wrote keeps working until the operator links a token in
# the swarm UI.
name = "the fetch never deletes the state-dir token, and swaps it in only on a change";
ok =
let
s = (fetchUnit agentGithubBao).script;
in
!(lib.hasInfix "rm -f \"$token\"" s)
&& lib.hasInfix "cmp -s \"$staged\" \"$token\"" s
&& lib.hasInfix "mv -f \"$staged\" \"$token\"" s;
}
{
# The poller reads the token once at start.
name = "the fetch runs before the github poller starts";
ok = builtins.elem "hive-github-notify.service" (fetchUnit agentGithubBao).before;
}
{
name = "the fetch re-runs every two minutes";
ok = agentGithubBao.systemd.timers.hive-agent-github-token.timerConfig.OnUnitInactiveSec == "2min";
}
];
in
runGroup "agent-github-bao" cases