agents: re-pull the agent's own bao mTLS leaf on a timer
swarm-controller re-issues an agent's store leaf at half its validity, but the running container kept presenting the copy its hive delivered at start, so a re-issued leaf reached the agent only on its next restart. hive-agent-bao-identity now seeds a live copy of the pair in its own runtime directory from the delivered credentials, and re-runs every 10 minutes: it logs in with the live leaf, reads its own swarm/agents/<agent>/bao-mtls (a read render_agent already grants), and when the leaf changed, logs in with the new pair and swaps it in only if the store accepts it. Every unit in the container that logs in to the store presents the live pair: hive-agent (queue-identity.nix), hive-matrix-daemon, hive-subagent-daemon, hive-agent-forge-token, hive-agent-forge-accounts and hive-agent-github-token. They keep loading the server CA as a credential. The long-running daemons are ordered after the refresher so the live pair exists when they start. It renews only while the held leaf is still valid: the login that reads the new leaf is made with the current one. Refs #5017
This commit is contained in:
parent
20509685a0
commit
c6717bccb0
17 changed files with 308 additions and 166 deletions
|
|
@ -87,20 +87,30 @@ let
|
|||
&& builtins.elem "hive-agent-bao-server-ca" c;
|
||||
}
|
||||
{
|
||||
# `%d` and not a path under the agent's state dir: the host file is
|
||||
# `0600` to the hive daemon, so the only copy this unprivileged unit can
|
||||
# open is the one systemd puts in its own credentials directory. The
|
||||
# address is the option's value rather than a literal that agrees with
|
||||
# it today.
|
||||
name = "the identity check presents its certificate out of the credentials directory";
|
||||
# The live pair sits in the unit's own runtime directory, which outlives
|
||||
# each run so the timer's next run logs in with what the last one
|
||||
# wrote. The address is the option's value rather than a literal that
|
||||
# agrees with it today.
|
||||
name = "the identity check presents the live pair it keeps in its runtime directory";
|
||||
ok =
|
||||
let
|
||||
e = (agentBaoIdentity agentBao).environment;
|
||||
u = agentBaoIdentity agentBao;
|
||||
e = u.environment;
|
||||
in
|
||||
e.BAO_CLIENT_CERT == "%d/hive-agent-bao-cert"
|
||||
&& e.BAO_CLIENT_KEY == "%d/hive-agent-bao-key"
|
||||
e.BAO_CLIENT_CERT == "/run/hive-agent-bao-identity/cert"
|
||||
&& e.BAO_CLIENT_KEY == "/run/hive-agent-bao-identity/key"
|
||||
&& u.serviceConfig.RuntimeDirectory == "hive-agent-bao-identity"
|
||||
&& u.serviceConfig.RuntimeDirectoryPreserve == "yes"
|
||||
&& e.BAO_ADDR == agentBao.services.hyperhive.agent.bao.addr;
|
||||
}
|
||||
{
|
||||
# A leaf the controller re-issues reaches a running container only
|
||||
# through this re-run, and an active unit cannot be started again.
|
||||
name = "the identity check re-runs on a timer";
|
||||
ok =
|
||||
(agentBaoIdentity agentBao).serviceConfig.RemainAfterExit == false
|
||||
&& agentBao.systemd.timers.hive-agent-bao-identity.timerConfig.OnUnitInactiveSec == "10min";
|
||||
}
|
||||
{
|
||||
# Same 403-not-a-miss reason as the hive-side readers: the path
|
||||
# `swarm_secret_client::mtls::identity_path` builds is the one this
|
||||
|
|
@ -133,22 +143,22 @@ let
|
|||
{
|
||||
# Nothing about the identity may be printed, and the read-back is where
|
||||
# that could slip: `bao kv get` on this path answers with certificate
|
||||
# material, and the object beside it is a private key. The check needs
|
||||
# only whether the read succeeded.
|
||||
# material and a private key. The answer goes to a file in the unit's
|
||||
# own `0700` directory.
|
||||
#
|
||||
# The path goes through `lib.escapeShellArg` here for the same reason the
|
||||
# module passes it through one — that helper decides whether an argument
|
||||
# needs quotes at all, and this one (only `[a-z0-9/-]`) comes back bare.
|
||||
# Spelling the quotes in by hand asserts a rendering nixpkgs chooses
|
||||
# rather than the redirect this property is about.
|
||||
name = "the identity check discards what it reads back";
|
||||
name = "the identity check writes what it reads back to a file";
|
||||
ok =
|
||||
let
|
||||
m = agentBao;
|
||||
name = m.services.hyperhive.agent.user.name;
|
||||
arg = lib.escapeShellArg "secret/swarm/agents/${name}/bao-mtls";
|
||||
in
|
||||
lib.hasInfix "bao kv get -field=cert ${arg} >/dev/null" (agentBaoIdentity m).script;
|
||||
lib.hasInfix "bao kv get -format=json ${arg} >\"$raw\"" (agentBaoIdentity m).script;
|
||||
}
|
||||
{
|
||||
# The absence arm, and what makes the four above able to fail. An agent
|
||||
|
|
@ -160,17 +170,16 @@ let
|
|||
}
|
||||
{
|
||||
# The harness reads its per-agent queue secret from the store itself,
|
||||
# under the same store identity the check above proves, because there is
|
||||
# one identity per agent.
|
||||
# under the same store identity the check above keeps current, because
|
||||
# there is one identity per agent.
|
||||
name = "the harness presents the agent's own store identity";
|
||||
ok =
|
||||
let
|
||||
u = agentHarness agentBao;
|
||||
in
|
||||
builtins.elem "hive-agent-bao-cert" u.serviceConfig.LoadCredential
|
||||
&& builtins.elem "hive-agent-bao-key" u.serviceConfig.LoadCredential
|
||||
&& u.environment.BAO_CLIENT_CERT == "%d/hive-agent-bao-cert"
|
||||
&& u.environment.BAO_CLIENT_KEY == "%d/hive-agent-bao-key"
|
||||
u.environment.BAO_CLIENT_CERT == "/run/hive-agent-bao-identity/cert"
|
||||
&& u.environment.BAO_CLIENT_KEY == "/run/hive-agent-bao-identity/key"
|
||||
&& builtins.elem "hive-agent-bao-identity.service" u.after
|
||||
&& u.environment.BAO_ADDR == agentBao.services.hyperhive.agent.bao.addr;
|
||||
}
|
||||
{
|
||||
|
|
@ -200,7 +209,7 @@ let
|
|||
in
|
||||
!(u.environment ? BAO_ADDR)
|
||||
&& !(u.environment ? HIVE_AGENT_NAME)
|
||||
&& !(builtins.elem "hive-agent-bao-cert" (u.serviceConfig.LoadCredential or [ ]));
|
||||
&& !(builtins.elem "hive-agent-bao-server-ca" (u.serviceConfig.LoadCredential or [ ]));
|
||||
}
|
||||
];
|
||||
in
|
||||
|
|
|
|||
Loading…
Reference in a new issue