diff --git a/nix/host-modules/swarm-bao.nix b/nix/host-modules/swarm-bao.nix index b02149c5..aebab744 100644 --- a/nix/host-modules/swarm-bao.nix +++ b/nix/host-modules/swarm-bao.nix @@ -180,9 +180,9 @@ let # `secret`, and the engine inserts `data/`. Matching the code's spelling # literally would grant nothing. # - # `read` too: `mint_and_verify` reads a credential back before writing so a - # re-run keeps the value a live agent already holds instead of rotating it — - # the read is required, not incidental. + # Write-only on purpose. The controller mints these; nothing in its job reads + # one back, and a read capability here would let it recover every agent's + # credentials rather than merely replace them. controllerPolicyText = '' path "auth/cert/certs/hive-*" { capabilities = ["create", "update", "read", "delete"] @@ -201,7 +201,7 @@ let } path "${credentialMountPath}/data/swarm/agents/*" { - capabilities = ["create", "read", "update"] + capabilities = ["create", "update"] } ''; diff --git a/nix/module-eval/bao-grants.nix b/nix/module-eval/bao-grants.nix index 0d4075dd..c03e0fcf 100644 --- a/nix/module-eval/bao-grants.nix +++ b/nix/module-eval/bao-grants.nix @@ -182,13 +182,11 @@ let && !(lib.hasInfix "sys/policies/acl" 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.) + # 🩸 `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. name = "matrix-ctl may read back the one path it writes"; ok = let @@ -475,18 +473,16 @@ let && !(lib.hasInfix "path \"secret/*\"" s); } { - # 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"; + # 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"; ok = let s = baoGrantHere.systemd.services.swarm-bao-controller-policy.script; in - lib.hasInfix "path \"secret/data/swarm/agents/*\" {\n capabilities = [\"create\", \"read\", \"update\"]" s; + lib.hasInfix "path \"secret/data/swarm/agents/*\" {\n capabilities = [\"create\", \"update\"]" s; } { # The policy above grants paths under a mount nothing else creates, so