Watch
0
0
Fork
You've already forked hyperhive
0

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
This commit is contained in:
atlas 2026-10-11 03:05:59 +02:00 • committed by mara
commit c6717bccb0
17 changed files with 308 additions and 166 deletions

View file

@ -1,6 +1,7 @@
# `checks.script-test-agent-bao-fetch` — runs the agent unit that logs in to
# the swarm secret store and fetches a secret, ../agent-modules/forge-token.nix,
# against a stub `bao`. The cases are in ./agent-bao-fetch.sh.
# `checks.script-test-agent-bao-fetch` — runs the agent units that log in to
# the swarm secret store and fetch a secret, ../agent-modules/forge-token.nix
# and ../agent-modules/bao.nix, against a stub `bao`. The cases are in
# ./agent-bao-fetch.sh.
#
# What runs is the unit's rendered `ExecStart`, on the unit's own `PATH` with
# the stub in front. The one edit is the unit's `/run/<unit>/` prefix, moved
@ -42,11 +43,16 @@ let
# Answers `login` and `kv get` from `FAKE_BAO_*` variables and logs every
# call. A `kv` call without the token `login` printed fails, so a script that
# drops `BAO_TOKEN` between the two cannot pass.
# drops `BAO_TOKEN` between the two cannot pass. A login presenting the
# certificate `FAKE_BAO_REFUSE_CERT` is refused.
fakeBao = pkgs.writeShellScriptBin "bao" ''
echo "$*" >> "$FAKE_BAO_LOG"
case "$1" in
login)
if [ -n "''${FAKE_BAO_REFUSE_CERT-}" ] && [ "$(<"$BAO_CLIENT_CERT")" = "$FAKE_BAO_REFUSE_CERT" ]; then
echo "fake bao: certificate refused" >&2
exit 2
fi
printf '%s' "$FAKE_BAO_LOGIN_ERR" >&2
printf '%s' "$FAKE_BAO_LOGIN_OUT"
exit "$FAKE_BAO_LOGIN_RC"
@ -70,6 +76,7 @@ in
pkgs.runCommand "hyperhive-script-test-agent-bao-fetch"
(
unitEnv "FORGE" "hive-agent-forge-token"
// unitEnv "IDENT" "hive-agent-bao-identity"
// {
FAKE_BAO_BIN = "${fakeBao}/bin";
}

View file

@ -38,6 +38,13 @@ setup() {
mkdir "$creds"
printf cert >"$creds/hive-agent-bao-cert"
printf key >"$creds/hive-agent-bao-key"
# The identity pair the unit presents: the delivered one for the fetch, the
# live copy it keeps for the identity unit.
if [ "$prefix" = IDENT ]; then
client_cert=$rt/cert client_key=$rt/key
else
client_cert=$creds/hive-agent-bao-cert client_key=$creds/hive-agent-bao-key
fi
: >"$log"
if ! sed "s|/run/$unit/|$rt/|g" "${!script_var}" >"$dir/script"; then
echo "cannot read the rendered script for $unit" >&2
@ -53,6 +60,7 @@ setup() {
kv_rc=0
kv_out=the-secret
kv_err=
refuse_cert=
}
go() {
@ -63,6 +71,8 @@ go() {
exec env -i \
PATH="$FAKE_BAO_BIN:$unit_path" \
BAO_ADDR="$bao_addr" \
BAO_CLIENT_CERT="$client_cert" \
BAO_CLIENT_KEY="$client_key" \
CREDENTIALS_DIRECTORY="$creds" \
FAKE_BAO_LOG="$log" \
FAKE_BAO_LOGIN_RC="$login_rc" \
@ -71,6 +81,7 @@ go() {
FAKE_BAO_KV_RC="$kv_rc" \
FAKE_BAO_KV_OUT="$kv_out" \
FAKE_BAO_KV_ERR="$kv_err" \
FAKE_BAO_REFUSE_CERT="$refuse_cert" \
"$dir/script"
) >"$dir/out" 2>"$dir/err"
rc=$?
@ -234,6 +245,68 @@ expect_out "fetched this agent's forge token"
expect_file "$rt/token" the-secret
expect_no_file "$rt/token.new"
ident_path=secret/swarm/agents/a1/bao-mtls
ident_kv_call="kv get -format=json $ident_path"
leaf() { printf '{"data":{"data":{"cert":"%s","key":"%s"}}}' "$1" "$2"; }
setup IDENT "no store identity delivered"
: >"$creds/hive-agent-bao-cert"
go
expect_rc 1
expect_err "has no store identity"
expect_calls
expect_no_file "$rt/cert"
setup IDENT "a first run seeds the live pair from the delivered one"
kv_out=$(leaf cert key)
go
expect_rc 0
expect_out "is unchanged"
expect_calls "$login_call" "$ident_kv_call"
expect_file "$rt/cert" cert
expect_file "$rt/key" key
if [ "$(stat -c %a "$rt/key")" != 400 ]; then fail "key is mode $(stat -c %a "$rt/key"), expected 400"; fi
expect_no_file "$rt/bao-mtls.json"
setup IDENT "the live pair outlives the delivered one"
printf cert2 >"$rt/cert"
printf key2 >"$rt/key"
chmod 0400 "$rt/cert" "$rt/key"
kv_out=$(leaf cert2 key2)
go
expect_rc 0
expect_out "is unchanged"
expect_file "$rt/cert" cert2
setup IDENT "a re-issued leaf replaces the live pair once a login with it succeeds"
kv_out=$(leaf cert2 key2)
go
expect_rc 0
expect_out "replaced this agent's store certificate"
expect_calls "$login_call" "$ident_kv_call" "$login_call"
expect_file "$rt/cert" cert2
expect_file "$rt/key" key2
expect_no_file "$rt/cert.new"
expect_no_file "$rt/key.new"
setup IDENT "a re-issued leaf the store refuses is not swapped in"
kv_out=$(leaf cert2 key2)
refuse_cert=cert2
go
expect_rc 1
expect_err "refused the certificate at $ident_path"
expect_file "$rt/cert" cert
expect_file "$rt/key" key
expect_no_file "$rt/cert.new"
setup IDENT "an object without a key keeps the live pair"
kv_out='{"data":{"data":{"cert":"cert2"}}}'
go
expect_rc 1
expect_err "holds no certificate and key"
expect_file "$rt/cert" cert
expect_file "$rt/key" key
if [ "$failures" -ne 0 ]; then
echo "script-test-agent-bao-fetch: $failures failed assertions over $count runs" >&2
exit 1