swarm-bao: grant the controller read on the agent credential prefix

mint_and_verify reads the queue credential back before writing it, so a
re-run keeps the value a live agent already authenticates with. that read
is read_optional, which maps only a 404 to absence — so with create/update
alone every mint aborted on a 403 at its first store read.

read on the same paths the stanza already grants create and update, and
nothing else: no list, no delete, no patch.
This commit is contained in:
atlas 2026-09-23 17:59:34 +02:00 committed by mara
commit 87174863ca
2 changed files with 27 additions and 15 deletions

View file

@ -182,11 +182,13 @@ let
&& !(lib.hasInfix "sys/policies/acl" s);
}
{
# 🩸 `read` is load-bearing here and is the one capability neither
# sibling has. matrix-ctl's first act is to read this path back and stop
# if something is there — that read IS "and only once", so without the
# capability every container restart would mint a second access token and
# invalidate the hive's.
# 🩸 `read` is load-bearing here, and the publisher — the one sibling
# that still has no `read` — shows what its absence costs. matrix-ctl's
# first act is to read this path back and stop if something is there —
# that read IS "and only once", so without the capability every container
# restart would mint a second access token and invalidate the hive's.
# (The controller holds `read` for the same idempotency reason, on the
# agent prefix.)
name = "matrix-ctl may read back the one path it writes";
ok =
let
@ -473,16 +475,18 @@ let
&& !(lib.hasInfix "path \"secret/*\"" s);
}
{
# Write-only is the property, not an accident of how it was typed: a
# `read` here would let the controller recover every agent's credentials
# instead of only replacing them. Pinned as the whole capability list,
# because an added capability is exactly what a presence check misses.
name = "the controller's grant on agent credentials is write-only";
# The exact list is the property, not an accident of how it was typed.
# `read` is in it because `mint_and_verify` reads a queue credential back
# before rewriting it; `list` is not, so the controller can fetch a
# credential only for an agent it was handed the name of, never enumerate
# the tree. Pinned as the whole capability list, because an added
# capability is exactly what a presence check misses.
name = "the controller's grant on agent credentials is create/read/update and nothing else";
ok =
let
s = baoGrantHere.systemd.services.swarm-bao-controller-policy.script;
in
lib.hasInfix "path \"secret/data/swarm/agents/*\" {\n capabilities = [\"create\", \"update\"]" s;
lib.hasInfix "path \"secret/data/swarm/agents/*\" {\n capabilities = [\"create\", \"read\", \"update\"]" s;
}
{
# The policy above grants paths under a mount nothing else creates, so