module-eval: pin the agent forge-token fetch and tea-login's removal
Refs #3782
This commit is contained in:
parent
b5d07d4df2
commit
6d0c30ade2
2 changed files with 173 additions and 0 deletions
|
|
@ -108,6 +108,10 @@ in
|
|||
inherit pkgs self nixosSystem;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
module-eval-agent-forge-bao = import ./module-eval/agent-forge-bao.nix {
|
||||
inherit pkgs self nixosSystem;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
module-eval-agent-memory = import ./module-eval/agent-memory.nix {
|
||||
inherit pkgs self nixosSystem;
|
||||
inherit (pkgs) lib;
|
||||
|
|
|
|||
169
nix/module-eval/agent-forge-bao.nix
Normal file
169
nix/module-eval/agent-forge-bao.nix
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# `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
|
||||
Loading…
Reference in a new issue