Watch
0
0
Fork
You've already forked hyperhive
0

give cert-auth role tokens a 15m ttl so logins stop leaving 32-day leases

This commit is contained in:
atlas 2026-10-28 18:32:44 +01:00 • committed by mara
commit 00df3ccffb
2 changed files with 160 additions and 30 deletions

View file

@ -988,6 +988,15 @@ let
# live agent's leaf once it is past half of it (`agent_renewal.rs`).
agentPkiLeafTtl = "168h";
# Lifetime of a token from a cert login, on every cert-auth role this file
# writes except the batch ones. The store keeps a lease per token until it
# expires; a login per operation at the mount default (32 days) leaves one
# lease each for a month. Every holder logs in, works and drops the token
# within two minutes (the controller's forge work is cut off at
# `FORGE_DEADLINE`). Rust-written roles take `CERT_ROLE_TOKEN_TTL`, which
# is the same value.
certRoleTokenTtl = "15m";
# The agent CA's certificate, cached on this host by `swarm-bao-agent-pki`,
# so `swarm-bao-certs` can compose the listener's bundle before the store
# is up. Public material.
@ -1372,15 +1381,18 @@ let
readerRoleWrite =
obj:
let
batchArgs = lib.optionalString (obj.batchToken or false
) " \\\n token_type=batch \\\n token_ttl=5m";
ttlArgs =
if obj.batchToken or false then
" \\\n token_type=batch \\\n token_ttl=5m"
else
" \\\n token_ttl=${certRoleTokenTtl} \\\n token_max_ttl=${certRoleTokenTtl}";
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}
display_name=${lib.escapeShellArg obj.cn}${ttlArgs}
'';
# A unit that logs in as the granter and runs `body`, ordered after the
@ -3660,7 +3672,9 @@ in
certificate=@${tlsDir}/client-ca.pem \
allowed_common_names=${lib.escapeShellArg controllerCn} \
token_policies=${lib.escapeShellArg controllerPolicyName} \
display_name=${lib.escapeShellArg controllerCn}
display_name=${lib.escapeShellArg controllerCn} \
token_ttl=${certRoleTokenTtl} \
token_max_ttl=${certRoleTokenTtl}
'';
};
@ -3684,7 +3698,9 @@ in
certificate=@${tlsDir}/client-ca.pem \
allowed_common_names=${lib.escapeShellArg secretPublisherCn} \
token_policies=${lib.escapeShellArg secretPublisherPolicyName} \
display_name=${lib.escapeShellArg secretPublisherCn}
display_name=${lib.escapeShellArg secretPublisherCn} \
token_ttl=${certRoleTokenTtl} \
token_max_ttl=${certRoleTokenTtl}
'';
# A THIRD sibling, for the reason the second one's comment gives: these
@ -3703,7 +3719,9 @@ in
certificate=@${tlsDir}/client-ca.pem \
allowed_common_names=${lib.escapeShellArg matrixCtlCn} \
token_policies=${lib.escapeShellArg matrixCtlPolicyName} \
display_name=${lib.escapeShellArg matrixCtlCn}
display_name=${lib.escapeShellArg matrixCtlCn} \
token_ttl=${certRoleTokenTtl} \
token_max_ttl=${certRoleTokenTtl}
'';
# 🩸 The principals that each log in with a leaf of their own, not as the
@ -3757,7 +3775,9 @@ in
certificate=@${tlsDir}/client-ca.pem \
allowed_common_names=${lib.escapeShellArg servicesIssuerCn} \
token_policies=${lib.escapeShellArg servicesIssuerPolicyName} \
display_name=${lib.escapeShellArg servicesIssuerCn}
display_name=${lib.escapeShellArg servicesIssuerCn} \
token_ttl=${certRoleTokenTtl} \
token_max_ttl=${certRoleTokenTtl}
'';
# A FIFTH sibling: the queue's issuing role, its policy and its login
@ -3793,7 +3813,9 @@ in
certificate=@${tlsDir}/client-ca.pem \
allowed_common_names=${lib.escapeShellArg natsCn} \
token_policies=${lib.escapeShellArg natsPolicyName} \
display_name=${lib.escapeShellArg natsCn}
display_name=${lib.escapeShellArg natsCn} \
token_ttl=${certRoleTokenTtl} \
token_max_ttl=${certRoleTokenTtl}
'';
# A SIXTH sibling, the nats unit's shape once per hive: each hive's
@ -3829,7 +3851,9 @@ in
certificate=@${tlsDir}/client-ca.pem \
allowed_common_names=${lib.escapeShellArg obj.cn} \
token_policies=${lib.escapeShellArg obj.name} \
display_name=${lib.escapeShellArg obj.cn}
display_name=${lib.escapeShellArg obj.cn} \
token_ttl=${certRoleTokenTtl} \
token_max_ttl=${certRoleTokenTtl}
'') hiveLeafIssuers
);

View file

@ -885,9 +885,9 @@ let
{
# 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";
# same helper, keeps service tokens with the short TTL, 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 the CI runner's issues short service tokens";
ok =
let
script = unit: baoGrantHere.systemd.services.${unit}.script;
@ -907,7 +907,8 @@ let
&& 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);
&& lib.hasInfix "token_ttl=15m" ci
&& lib.hasInfix "token_max_ttl=15m" ci;
}
{
name = "the PKI unit signs the metrics store responder's leaf under its own subject";
@ -1471,6 +1472,59 @@ let
&& !(lib.hasPrefix "swarm-" cn)
&& !(lib.hasPrefix "hive-" cn);
}
{
# A cert login leaves a lease until its token expires, and a role without
# `token_ttl` gets the mount's default of 32 days. Every role write in
# every unit is read, so the TTL must be on the command itself, not
# somewhere else in the script. A role is a service token at `15m`, or a
# batch token at `5m`.
#
# The presence control: the roles found include the controller's, the
# granter's, a batch responder's, the CI runner's and a per-hive leaf
# role, so a changed spelling of the write cannot leave the loop with
# nothing to check.
name = "every cert-auth role the units write issues short-lived tokens";
ok =
let
roleWrites =
cfg:
lib.concatMap (
u:
let
chunks = lib.tail (lib.splitString "bao write auth/cert/certs/" u.script);
commandOf =
chunk:
let
ls = lib.splitString "\n" chunk;
last = lib.lists.findFirstIndex (l: !(lib.hasSuffix "\\" l)) 0 ls;
in
lib.concatStringsSep "\n" (lib.take (last + 1) ls);
in
map (c: {
role = lib.head (lib.splitString " " c);
command = commandOf c;
}) chunks
) (lib.attrValues cfg.systemd.services);
writes = roleWrites baoGrantHere ++ roleWrites baoGrantTwoHives;
short =
w:
(lib.hasInfix "token_ttl=15m" w.command && lib.hasInfix "token_max_ttl=15m" w.command)
|| (
lib.hasInfix "token_type=batch" w.command
&& lib.hasInfix "token_ttl=5m" w.command
&& !(lib.hasInfix "token_max_ttl" w.command)
);
roles = map (w: w.role) writes;
in
lib.all short writes
&& lib.all (r: lib.elem r roles) [
"swarm-controller"
"bao-granter"
"swarm-nats-auth"
"swarm-ci-runner"
"swarm-hive-leaf-h2"
];
}
{
# The other principals are what they were: no unit but the granter's own
# hands its policy to a role, and none of them logs in as it.