Watch
0
0
Fork
You've already forked hyperhive
0

hive-agent: read the per-agent queue secret from bao in process

The harness now reads swarm/agents/<agent>/queue from the store itself,
under the agent's own store certificate, and holds it in memory only.
It reads once before the first connect and again on every reconnect
attempt (async-nats `ConnectOptions::with_auth_callback`), so an agent
whose secret was re-minted reconnects with the new value instead of
being refused until the container restarts.

hive-agent-queue-credential.service, the /run file it wrote, and
HIVE_AGENT_QUEUE_AGENT_SECRET_FILE are gone; queue-identity.nix now
hands hive-agent.service the store address, its certificate paths and
the agent name.

A failed or empty read before the first connect still falls back to
the hive's shared client. Each read is bounded by a 10s timeout, and
retries wait out the existing reconnect backoff (500ms doubling, capped
at 60s).

Closes #4783
This commit is contained in:
atlas 2026-09-29 09:40:48 +02:00 • committed by mara
commit ccb5bd3b38
10 changed files with 770 additions and 529 deletions

View file

@ -1,9 +1,8 @@
# `checks.script-test-agent-bao-fetch` — runs the two agent units that log in
# to the swarm secret store and fetch a secret, ../agent-modules/forge-token.nix
# and ../agent-modules/queue-identity.nix, against a stub `bao`. The cases are
# in ./agent-bao-fetch.sh.
# `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.
#
# What runs is each unit's rendered `ExecStart`, on the unit's own `PATH` with
# 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
# under the build directory because the sandbox has no writable `/run`.
{
@ -71,7 +70,6 @@ in
pkgs.runCommand "hyperhive-script-test-agent-bao-fetch"
(
unitEnv "FORGE" "hive-agent-forge-token"
// unitEnv "QUEUE" "hive-agent-queue-credential"
// {
FAKE_BAO_BIN = "${fakeBao}/bin";
}

View file

@ -22,7 +22,7 @@ if (: >"$probe") 2>/dev/null; then
exit 1
fi
# setup FORGE|QUEUE <description>
# setup FORGE <description>
setup() {
prefix=$1
case="$prefix: $2"
@ -111,125 +111,108 @@ expect_no_file() {
if [ -e "$1" ]; then fail "$1 left behind"; fi
}
for prefix in FORGE QUEUE; do
if [ "$prefix" = FORGE ]; then
secret_name=token
path=secret/swarm/agents/a1/forge-token
missing_msg="no forge token at $path yet"
else
secret_name=secret
path=secret/swarm/agents/a1/queue
missing_msg="no per-agent queue credential at $path yet"
fi
login_call="login -method=cert -token-only"
kv_call="kv get -field=value $path"
secret_name=token
path=secret/swarm/agents/a1/forge-token
missing_msg="no forge token at $path yet"
login_call="login -method=cert -token-only"
kv_call="kv get -field=value $path"
setup "$prefix" "no store identity delivered"
: >"$creds/hive-agent-bao-cert"
go
expect_rc 0
expect_err "has no store identity"
expect_calls
setup FORGE "no store identity delivered"
: >"$creds/hive-agent-bao-cert"
go
expect_rc 0
expect_err "has no store identity"
expect_calls
setup "$prefix" "a credential that cannot be read"
chmod 0000 "$creds/hive-agent-bao-key"
go
expect_rc 1
expect_err "cannot read $creds/hive-agent-bao-key"
expect_calls
setup FORGE "a credential that cannot be read"
chmod 0000 "$creds/hive-agent-bao-key"
go
expect_rc 1
expect_err "cannot read $creds/hive-agent-bao-key"
expect_calls
setup "$prefix" "bao's stderr file cannot be created"
chmod 0500 "$rt"
go
chmod 0700 "$rt"
expect_rc 1
expect_err "could not create $rt/bao.err, so bao never ran"
expect_calls
setup FORGE "bao's stderr file cannot be created"
chmod 0500 "$rt"
go
chmod 0700 "$rt"
expect_rc 1
expect_err "could not create $rt/bao.err, so bao never ran"
expect_calls
setup "$prefix" "the store refuses the login with an HTTP 4xx"
login_rc=2
login_err=$'Error authenticating: Error making API request.\n\nURL: PUT '"$bao_addr"$'/v1/auth/cert/login\nCode: 403. Errors:\n\n* permission denied\n'
go
expect_rc 1
expect_err "refused this agent's certificate login with HTTP 403:"
expect_err "* permission denied"
expect_calls "$login_call"
setup FORGE "the store refuses the login with an HTTP 4xx"
login_rc=2
login_err=$'Error authenticating: Error making API request.\n\nURL: PUT '"$bao_addr"$'/v1/auth/cert/login\nCode: 403. Errors:\n\n* permission denied\n'
go
expect_rc 1
expect_err "refused this agent's certificate login with HTTP 403:"
expect_err "* permission denied"
expect_calls "$login_call"
setup "$prefix" "the store fails the login with another HTTP status"
login_rc=2
login_err=$'Error authenticating: Error making API request.\n\nCode: 500. Errors:\n\n* internal error\n'
go
expect_rc 1
expect_err "failed this agent's certificate login with HTTP 500:"
expect_err "* internal error"
expect_calls "$login_call"
setup FORGE "the store fails the login with another HTTP status"
login_rc=2
login_err=$'Error authenticating: Error making API request.\n\nCode: 500. Errors:\n\n* internal error\n'
go
expect_rc 1
expect_err "failed this agent's certificate login with HTTP 500:"
expect_err "* internal error"
expect_calls "$login_call"
setup "$prefix" "the store refuses the certificate with a TLS alert"
login_rc=2
login_err="Error authenticating: Put \"$bao_addr/v1/auth/cert/login\": remote error: tls: unknown certificate authority"
go
expect_rc 1
expect_err "refused this agent's certificate in the TLS handshake:"
expect_err "remote error: tls: unknown certificate authority"
expect_calls "$login_call"
setup FORGE "the store refuses the certificate with a TLS alert"
login_rc=2
login_err="Error authenticating: Put \"$bao_addr/v1/auth/cert/login\": remote error: tls: unknown certificate authority"
go
expect_rc 1
expect_err "refused this agent's certificate in the TLS handshake:"
expect_err "remote error: tls: unknown certificate authority"
expect_calls "$login_call"
setup "$prefix" "the store does not answer"
login_rc=2
login_err="Error authenticating: Put \"$bao_addr/v1/auth/cert/login\": dial tcp: lookup bao.t.local: no such host"
go
expect_rc 1
expect_err "got no answer from the swarm secret store at $bao_addr"
expect_err "no such host"
expect_calls "$login_call"
setup FORGE "the store does not answer"
login_rc=2
login_err="Error authenticating: Put \"$bao_addr/v1/auth/cert/login\": dial tcp: lookup bao.t.local: no such host"
go
expect_rc 1
expect_err "got no answer from the swarm secret store at $bao_addr"
expect_err "no such host"
expect_calls "$login_call"
# Only the forge unit keeps its runtime directory between runs; the queue
# unit starts each run with an empty one.
setup "$prefix" "nothing minted at the agent's path yet"
if [ "$prefix" = FORGE ]; then
printf old >"$rt/$secret_name"
chmod 0400 "$rt/$secret_name"
fi
kv_rc=2
kv_err="No value found at secret/data/swarm/agents/a1"
go
expect_rc 0
expect_err "$missing_msg"
expect_err "No value found"
expect_calls "$login_call" "$kv_call"
if [ "$prefix" = FORGE ]; then
expect_file "$rt/$secret_name" old
else
expect_no_file "$rt/$secret_name"
fi
# The unit keeps its runtime directory between runs.
setup FORGE "nothing minted at the agent's path yet"
printf old >"$rt/$secret_name"
chmod 0400 "$rt/$secret_name"
kv_rc=2
kv_err="No value found at secret/data/swarm/agents/a1"
go
expect_rc 0
expect_err "$missing_msg"
expect_err "No value found"
expect_calls "$login_call" "$kv_call"
expect_file "$rt/$secret_name" old
setup "$prefix" "a first fetch writes the secret 0400"
go
expect_rc 0
expect_out "fetched this agent's"
expect_no_err "Permission denied"
expect_calls "$login_call" "$kv_call"
expect_file "$rt/$secret_name" the-secret
if [ "$(stat -c %a "$rt/$secret_name")" != 400 ]; then fail "$secret_name is mode $(stat -c %a "$rt/$secret_name"), expected 400"; fi
expect_no_file "$rt/bao.err"
expect_no_file "$rt/token.new"
setup FORGE "a first fetch writes the secret 0400"
go
expect_rc 0
expect_out "fetched this agent's"
expect_no_err "Permission denied"
expect_calls "$login_call" "$kv_call"
expect_file "$rt/$secret_name" the-secret
if [ "$(stat -c %a "$rt/$secret_name")" != 400 ]; then fail "$secret_name is mode $(stat -c %a "$rt/$secret_name"), expected 400"; fi
expect_no_file "$rt/bao.err"
expect_no_file "$rt/token.new"
# `UMask=0377` makes every file the script creates 0400, the login's own
# `bao.err` included, and a redirect into a 0400 file fails before bao starts.
setup "$prefix" "0400 files already in place do not block the fetch"
printf stale >"$rt/bao.err"
chmod 0400 "$rt/bao.err"
if [ "$prefix" = FORGE ]; then
printf stale >"$rt/token.new"
chmod 0400 "$rt/token.new"
fi
go
expect_rc 0
expect_out "fetched this agent's"
expect_no_err "Permission denied"
expect_no_err "refused"
expect_calls "$login_call" "$kv_call"
expect_file "$rt/$secret_name" the-secret
done
# `UMask=0377` makes every file the script creates 0400, the login's own
# `bao.err` included, and a redirect into a 0400 file fails before bao starts.
setup FORGE "0400 files already in place do not block the fetch"
printf stale >"$rt/bao.err"
chmod 0400 "$rt/bao.err"
printf stale >"$rt/token.new"
chmod 0400 "$rt/token.new"
go
expect_rc 0
expect_out "fetched this agent's"
expect_no_err "Permission denied"
expect_no_err "refused"
expect_calls "$login_call" "$kv_call"
expect_file "$rt/$secret_name" the-secret
setup FORGE "an unchanged token is left in place"
printf the-secret >"$rt/token"