Watch
0
0
Fork
You've already forked hyperhive
0

swarm-bao: the responder may create the callout xkey; every token reads swarm/public/

The swarm-nats-auth role gains create+read on exactly the callout xkey's
two leaves, and no update, so a stored pair is never replaced.

swarm/public/ is readable by every token through bao's default policy.
swarm-bao-public-read-policy reads that policy, replaces only its own
marker-delimited block, and writes it back when it differs; it never
writes a policy it read back empty. Writing `default` is a new grant on
the granter's own policy, so on an existing store it lands only after
the bootstrap-token step re-runs; until then the unit says so and exits
0, and swarm-bao-granter-role restarts it on success.
This commit is contained in:
atlas 2026-10-05 21:25:40 +02:00 • committed by mara
commit bb571285fd
3 changed files with 258 additions and 28 deletions

View file

@ -170,8 +170,8 @@ let
_: u: (u.environment.BAO_CLIENT_CERT or null) == granterCertFile
) baoGrantWithConsumers.systemd.services;
# The fourteen units that write a `swarm-*` grant, by name, for the discovery
# control below.
# The fifteen units that write a grant as the granter, by name, for the
# discovery control below.
grantingUnitNames = [
"swarm-bao-controller-policy"
"swarm-bao-secret-publisher-policy"
@ -187,6 +187,7 @@ let
"swarm-bao-operator-viewer-policy"
"swarm-bao-hive-leaf-policy"
"swarm-bao-ci-runner-policy"
"swarm-bao-public-read-policy"
];
# Comment lines dropped first: both the HCL and the scripts explain
@ -573,16 +574,33 @@ let
# `+` is one path segment, so this reaches `swarm/agents/<agent>/queue`
# and no other credential an agent holds; `swarm/agents/*` would reach
# all of them. The responder's own OIDC client secret is the one
# `services/` leaf beside it.
name = "the queue responder's grant is every agent's queue credential and its own OIDC secret, nothing else";
# `services/` leaf beside it. Its only creates are the callout xkey's
# two leaves, and nothing it holds may update or delete one.
name = "the queue responder reads agent queue credentials and its OIDC secret, and may create only the callout xkey's two leaves";
ok =
let
s = baoGrantHere.systemd.services.swarm-bao-nats-auth-policy.script;
grants = grantsIn s;
creates = map (g: g.path) (lib.filter (g: lib.elem "create" g.caps) grants);
in
lib.hasInfix "path \"secret/data/swarm/agents/+/queue\" {" s
&& lib.hasInfix "path \"secret/data/swarm/services/swarm-nats/oidc/client\" {" s
&& lib.length (lib.filter lib.isList (builtins.split "capabilities = \\[\"read\"]" s)) == 2
&& lib.length (lib.filter lib.isList (builtins.split "path \"" s)) == 2
&&
lib.sort lib.lessThan creates == [
"secret/data/swarm/public/swarm-nats/callout-xkey"
"secret/data/swarm/services/swarm-nats/callout/xkey-seed"
]
&& lib.all (
g:
g.caps == [ "read" ]
||
g.caps == [
"create"
"read"
]
) grants
&& lib.length (lib.filter lib.isList (builtins.split "path \"" s)) == 4
&& !(lib.hasInfix "secret/data/swarm/agents/*" s)
&& !(lib.hasInfix "secret/data/swarm/hives" s)
&& !(lib.hasInfix "secret/data/swarm/services/*" s)
@ -795,24 +813,24 @@ let
}
{
# A store host without the granter's pair writes its grants some other
# way, so none of the fourteen units may exist. Without this arm
# way, so none of the fifteen units may exist. Without this arm
# `lib.mkIf haveGranter` could be dropped from any of them and every other
# case here would still pass.
name = "without the granter's pair none of the fourteen granting units render";
name = "without the granter's pair none of the fifteen granting units render";
ok =
let
s = baoGranterOptOut.systemd.services;
in
lib.all (unit: !(s ? ${unit})) (grantingUnitNames ++ [ "swarm-bao-granter-role" ])
# The control: the same store with the pair renders all fourteen.
# The control: the same store with the pair renders all fifteen.
&& lib.all (unit: baoGrantHere.systemd.services ? ${unit}) grantingUnitNames;
}
{
# 🩸 What replaced the silent skip. With no bootstrap token the fourteen still
# 🩸 What replaced the silent skip. With no bootstrap token the fifteen still
# render, and a refused granter fails them with the step that fixes it.
# A store host that never named a token is told to name one, since the
# unit that sets the granter up renders only where it has.
name = "a store host without a bootstrap token renders the fourteen, each failing loudly with the one-time step";
name = "a store host without a bootstrap token renders the fifteen, each failing loudly with the one-time step";
ok =
let
s = baoGranterNoToken.systemd.services;
@ -919,7 +937,7 @@ let
{
# The granter's grants, whole. Pinned as the full list, because an added
# path or capability is exactly what a presence check misses.
name = "the granter's policy is exactly these twenty stanzas";
name = "the granter's policy is exactly these twenty-one stanzas";
ok =
let
cu = [
@ -1011,6 +1029,14 @@ let
path = "secret/data/swarm/services/swarm-bao-ui/oidc/client";
caps = [ "read" ];
}
{
path = "sys/policies/acl/default";
caps = [
"create"
"read"
"update"
];
}
];
}
{
@ -1025,9 +1051,10 @@ let
];
}
{
# Outside `swarm-*` and the store's own mounts it holds nothing: no
# hive's policy or role, no auth mount, no token, no secret.
name = "the granter grants nothing outside swarm-* and the store's own mounts";
# Outside `swarm-*`, the store's own mounts and the `default` policy it
# holds nothing: no hive's policy or role, no auth mount, no token, no
# secret.
name = "the granter grants nothing outside swarm-*, the store's own mounts and the default policy";
ok =
lib.all (p: grantFor granterGrants p == null) [
"sys/policies/acl/hive-x"
@ -1443,8 +1470,8 @@ let
}
{
# What makes the case above mean something: discovery by the granter's
# certificate reaches all fourteen units, and each yields calls.
name = "the granter-policy check sees all fourteen granting units, and parses calls from each";
# certificate reaches all fifteen units, and each yields calls.
name = "the granter-policy check sees all fifteen granting units, and parses calls from each";
ok =
lib.sort lib.lessThan (lib.attrNames granterUnits) == lib.sort lib.lessThan grantingUnitNames
&& lib.all (u: baoCalls u.script != [ ]) (lib.attrValues granterUnits)
@ -1706,6 +1733,77 @@ let
&& !(lib.elem "swarm-bao-operator-viewer-policy.service" g.after)
&& !(v ? postStart && lib.hasInfix "swarm-bao-granter-role" v.postStart);
}
{
# Every token carries bao's `default` policy, so a read added there is
# a read for every authenticated client. Exactly one stanza, a read on
# `swarm/public/` and nothing wider; the markers are what a later run
# replaces it by.
name = "the default policy gains one read, on swarm/public/, between markers";
ok =
let
s = baoGrantHere.systemd.services.swarm-bao-public-read-policy.script;
block = lib.head (
lib.tail (
lib.splitString "# hyperhive: swarm/public begin\n" (builtins.unsafeDiscardStringContext s)
)
);
stanzas = grantsIn (lib.head (lib.splitString "# hyperhive: swarm/public end" block));
in
stanzas == [
{
path = "secret/data/swarm/public/*";
caps = [ "read" ];
}
]
&& lib.hasInfix "-v e='# hyperhive: swarm/public end'" s
&& lib.hasInfix "printf '%s\\n' \"$wanted\" |\n bao policy write default -" s;
}
{
# A read-modify-write of a policy every token depends on: the read
# comes first, an empty one stops the unit before anything is written,
# and the capability probe comes before both, so a granter without the
# grant writes nothing.
name = "the default policy is read before it is written, and never written from an empty read";
ok =
let
s = builtins.unsafeDiscardStringContext baoGrantHere.systemd.services.swarm-bao-public-read-policy.script;
at = needle: lib.stringLength (lib.head (lib.splitString needle s));
probe = "caps=\"$(bao token capabilities sys/policies/acl/default)\"";
read = "bao read -field=policy sys/policies/acl/default";
emptyGuard = "if [ -z \"$current\" ]; then";
write = "bao policy write default -";
in
lib.hasInfix probe s
&& at probe < at "exit 0"
&& at "exit 0" < at read
&& at read < at emptyGuard
&& at emptyGuard < at write
&& lib.hasInfix "swarm-bao-granter-role re-runs this unit once it succeeds." s;
}
{
# The granter's own policy is what lets that unit write `default`, and a
# changed granter policy lands only with the bootstrap step, so the
# step's success restarts the unit as it does the viewer's.
name = "the granter may write the default policy, and its step restarts the public-read unit";
ok =
let
s = baoGrantHere.systemd.services;
g = s.swarm-bao-granter-role;
p = s.swarm-bao-public-read-policy;
in
lib.any (
gr:
gr.path == "sys/policies/acl/default"
&&
gr.caps == [
"create"
"read"
"update"
]
) granterGrants
&& lib.hasInfix "swarm-bao-public-read-policy.service" g.serviceConfig.ExecStartPost
&& lib.elem "swarm-bao-granter-role.service" p.after;
}
{
# The client secret is on stdin, never an argument in /proc; the rest is
# the swarm's authelia and the UI's own client id.