Watch
0
0
Fork
You've already forked hyperhive
0

swarm-bao: the agent-token checkers' cert roles issue batch tokens

This commit is contained in:
atlas 2026-10-17 15:08:02 +02:00 • committed by mara
commit de4871cab1
3 changed files with 56 additions and 7 deletions

View file

@ -465,6 +465,13 @@ names the hive's own `clientCertFile` and misses a principal's pair has the
build refused, by the module of the reader it missed, naming that pair — see build refused, by the module of the reader it missed, naming that pair — see
[what a missing leaf costs](#what-a-missing-leaf-costs) below. [what a missing leaf costs](#what-a-missing-leaf-costs) below.
The roles of `swarm-nats-auth`, `swarm-logs-auth` and `swarm-metrics-auth` issue
batch tokens with a five-minute TTL (`token_type=batch`, `token_ttl=5m`). The
responders log in on every check, and the store keeps no record of a batch token,
so unauthenticated requests don't grow its token storage. Nothing can renew
or revoke a batch token. The other roles keep service tokens, because some of
their readers hold a handle for longer than five minutes.
### What a missing leaf costs ### What a missing leaf costs
Each of the three modules asserts its own pair, on the same condition: **this host Each of the three modules asserts its own pair, on the same condition: **this host

View file

@ -1001,6 +1001,7 @@ let
{ {
name = "swarm-nats-auth"; name = "swarm-nats-auth";
cn = baoDeploy.natsAuthCommonName; cn = baoDeploy.natsAuthCommonName;
batchToken = true;
policyText = policyText =
readStanza "${credentialMountPath}/data/swarm/agents/+/queue" readStanza "${credentialMountPath}/data/swarm/agents/+/queue"
+ readStanza "${credentialMountPath}/data/swarm/services/${hyperhiveCfg.swarm.nats.clientId}/oidc/client" + readStanza "${credentialMountPath}/data/swarm/services/${hyperhiveCfg.swarm.nats.clientId}/oidc/client"
@ -1052,6 +1053,7 @@ let
{ {
name = "swarm-logs-auth"; name = "swarm-logs-auth";
cn = baoDeploy.logsAuthCommonName; cn = baoDeploy.logsAuthCommonName;
batchToken = true;
policyText = policyText =
readStanza "${credentialMountPath}/data/swarm/agents/+/queue" readStanza "${credentialMountPath}/data/swarm/agents/+/queue"
+ readStanza "${credentialMountPath}/data/${grafanaMetricsBearerLeaf}" + readStanza "${credentialMountPath}/data/${grafanaMetricsBearerLeaf}"
@ -1069,6 +1071,7 @@ let
{ {
name = "swarm-metrics-auth"; name = "swarm-metrics-auth";
cn = baoDeploy.metricsAuthCommonName; cn = baoDeploy.metricsAuthCommonName;
batchToken = true;
policyText = policyText =
readStanza "${credentialMountPath}/data/swarm/agents/+/queue" readStanza "${credentialMountPath}/data/swarm/agents/+/queue"
+ readStanza "${credentialMountPath}/data/${grafanaMetricsBearerLeaf}" + readStanza "${credentialMountPath}/data/${grafanaMetricsBearerLeaf}"
@ -1182,13 +1185,25 @@ let
# `swarm` in one — and ./swarm.nix reserves the concrete per-hive spellings # `swarm` in one — and ./swarm.nix reserves the concrete per-hive spellings
# anyway, because the prefixes are operator-settable and an operator may spell # anyway, because the prefixes are operator-settable and an operator may spell
# one without that substring. # one without that substring.
readerRoleWrite = obj: '' #
bao write auth/cert/certs/${lib.escapeShellArg obj.name} \ # `batchToken = true` on an object makes its logins batch tokens, which the
certificate=@${tlsDir}/client-ca.pem \ # store does not persist and cannot renew or revoke. Only for a principal
allowed_common_names=${lib.escapeShellArg obj.cn} \ # that logs in, reads and drops the token within one call: the five
token_policies=${lib.escapeShellArg obj.name} \ # minutes outlast that read, and a leaked token dies as fast. A principal
display_name=${lib.escapeShellArg obj.cn} # that holds a handle longer must not set it.
''; readerRoleWrite =
obj:
let
batchArgs = lib.optionalString (obj.batchToken or false
) " \\\n token_type=batch \\\n token_ttl=5m";
in
''
bao write auth/cert/certs/${lib.escapeShellArg obj.name} \
certificate=@${tlsDir}/client-ca.pem \
allowed_common_names=${lib.escapeShellArg obj.cn} \
token_policies=${lib.escapeShellArg obj.name} \
display_name=${lib.escapeShellArg obj.cn}${batchArgs}
'';
# A unit that logs in as the granter and runs `body`, ordered after the # A unit that logs in as the granter and runs `body`, ordered after the
# controller's unit. `after` and not `requires`: the controller's and the # controller's unit. `after` and not `requires`: the controller's and the

View file

@ -815,6 +815,33 @@ let
&& lib.hasInfix "allowed_common_names=swarm-metrics-auth" s && lib.hasInfix "allowed_common_names=swarm-metrics-auth" s
&& lib.hasInfix "token_policies=swarm-metrics-auth" s; && lib.hasInfix "token_policies=swarm-metrics-auth" s;
} }
{
# The three agent-token checkers log in once per check; a batch token
# leaves nothing stored per request. The CI runner's role, written by the
# same helper, keeps service tokens, and still carries its policy, so a
# misspelled script name cannot make the absence pass.
name = "the agent-token checkers' roles issue batch tokens and a holder's role does not";
ok =
let
script = unit: baoGrantHere.systemd.services.${unit}.script;
batch =
unit: role:
let
s = script unit;
in
lib.hasInfix "auth/cert/certs/${role} " s
&& lib.hasInfix "token_type=batch" s
&& lib.hasInfix "token_ttl=5m" s
&& !(lib.hasInfix "token_max_ttl" s);
ci = script "swarm-bao-ci-runner-policy";
in
batch "swarm-bao-nats-auth-policy" "swarm-nats-auth"
&& batch "swarm-bao-logs-auth-policy" "swarm-logs-auth"
&& batch "swarm-bao-metrics-auth-policy" "swarm-metrics-auth"
&& lib.hasInfix "token_policies=swarm-ci-runner" ci
&& !(lib.hasInfix "token_type" ci)
&& !(lib.hasInfix "token_ttl" ci);
}
{ {
name = "the PKI unit signs the metrics store responder's leaf under its own subject"; name = "the PKI unit signs the metrics store responder's leaf under its own subject";
ok = ok =