swarm: wire the agents' queue coordinates and credential through the modules
The host end: `HIVE_C0RE_AGENT_QUEUE_CREDENTIAL_DIR` tells the daemon where the reader unit put the files, and a new `deploy.hive-controller.queue.agentNatsUrl` says where the queue is as an agent *container* reaches it. That address defaults to the bridge one and never to loopback — `statusPublish.natsUrl` beside it is loopback and correct, because hive-c0re shares the host netns and an agent does not. Paired with the swarm's token endpoint, gated together, and forwarded by `hive_c0re::meta` as both an env var and an agent option: the harness reads the variable at runtime, its unit is built from the option. The agent end: `nix/agent-modules/queue.nix` declares that option pair and, when set, has the harness unit inherit the two credentials by name. Bare-id `LoadCredential=` is the terse form documented for inheriting what the service manager received, and is non-fatal when the credential is absent — which a hive whose publisher has not run yet needs. No `HIVE_AGENT_OIDC_CA_FILE`: the meta flake already embeds the hive CA and the swarm root into each container's trust store at build time, and reqwest's rustls backend verifies against it. Refs #3805
This commit is contained in:
parent
353cdd9264
commit
86652f051a
7 changed files with 290 additions and 0 deletions
|
|
@ -464,6 +464,16 @@ let
|
|||
otel.protocol = "grpc";
|
||||
};
|
||||
agentNoOtel = agent { };
|
||||
|
||||
# The agent side of the swarm queue. Both coordinates set is the only state
|
||||
# in which the harness unit declares a credential at all, so the pair and
|
||||
# the empty fixture beside it are the two arms worth having.
|
||||
agentQueue = agent {
|
||||
queue.natsUrl = "nats://10.42.0.1:4222";
|
||||
queue.tokenEndpoint = "https://auth.t.local/api/oidc/token";
|
||||
};
|
||||
agentNoQueue = agent { };
|
||||
agentHarness = machine: machine.systemd.services.hive-agent;
|
||||
agentSettings = machine: machine.services.opentelemetry-collector.settings;
|
||||
|
||||
# This hive's own collector, which is a HOST service — unlike the swarm
|
||||
|
|
@ -1242,6 +1252,87 @@ let
|
|||
&& !(builtins.elem "hive-c0re.service" (u.requiredBy or [ ]))
|
||||
&& !(builtins.elem "hive-c0re.service" (u.requires or [ ]));
|
||||
}
|
||||
{
|
||||
# Where the reader puts the files and where the daemon looks for them is
|
||||
# one agreement spanning two modules. Asserted against the option rather
|
||||
# than the literal so moving the directory moves both ends.
|
||||
name = "hive-c0re is told where the agents' queue credential lands";
|
||||
ok =
|
||||
allLocal.systemd.services.hive-c0re.environment.HIVE_C0RE_AGENT_QUEUE_CREDENTIAL_DIR
|
||||
== toString allLocal.services.hyperhive.deploy.hive-controller.queue.agentCredentialDir;
|
||||
}
|
||||
{
|
||||
# The one address in this file that must NOT be loopback. Both spellings
|
||||
# sit in the same unit's environment and are correct for their own
|
||||
# reader: hive-c0re shares the host netns, an agent container does not,
|
||||
# so a copy-paste between them reaches the agent itself and the symptom
|
||||
# is a connect that hangs.
|
||||
name = "the agents' queue address is the bridge, not the loopback one the hive itself uses";
|
||||
ok =
|
||||
let
|
||||
e = allLocal.systemd.services.hive-c0re.environment;
|
||||
in
|
||||
e.HIVE_AGENT_NATS_URL == "nats://${allLocal.services.hyperhive.network.bridgeIp}:4222"
|
||||
&& !(lib.hasInfix "127.0.0.1" e.HIVE_AGENT_NATS_URL)
|
||||
&& e.HIVE_AGENT_NATS_URL != e.HIVE_C0RE_NATS_URL;
|
||||
}
|
||||
{
|
||||
# The agents mint against the swarm's IdP, the same endpoint the hive's
|
||||
# own client uses — a hive-local guess would be a token no queue accepts.
|
||||
name = "the agents' token endpoint is the swarm IdP's";
|
||||
ok =
|
||||
let
|
||||
e = allLocal.systemd.services.hive-c0re.environment;
|
||||
in
|
||||
lib.hasSuffix "/api/oidc/token" e.HIVE_AGENT_OIDC_TOKEN_ENDPOINT
|
||||
&& e.HIVE_AGENT_OIDC_TOKEN_ENDPOINT == e.HIVE_C0RE_OIDC_TOKEN_ENDPOINT;
|
||||
}
|
||||
{
|
||||
# The absence arm, and what makes the two above able to fail: a hive
|
||||
# with no queue must forward neither coordinate, because half a pair
|
||||
# reaches the harness as a partial configuration rather than as none.
|
||||
name = "a hive with no swarm queue forwards no agent queue coordinates";
|
||||
ok =
|
||||
let
|
||||
e = bare.systemd.services.hive-c0re.environment;
|
||||
in
|
||||
!(e ? HIVE_AGENT_NATS_URL) && !(e ? HIVE_AGENT_OIDC_TOKEN_ENDPOINT);
|
||||
}
|
||||
{
|
||||
# Both ids or neither: the secret authenticates nobody without the id it
|
||||
# belongs to, and the harness refuses to treat one of the two as a queue.
|
||||
name = "an agent with queue coordinates imports both halves of its credential";
|
||||
ok =
|
||||
let
|
||||
c = (agentHarness agentQueue).serviceConfig.LoadCredential;
|
||||
in
|
||||
builtins.elem "hive-queue-agent-secret" c && builtins.elem "hive-queue-agent-client-id" c;
|
||||
}
|
||||
{
|
||||
# `%d` and not a path under the state dir: the host file is `0600`
|
||||
# root-owned, so the only copy this unprivileged unit can open is the
|
||||
# one systemd puts in its own credentials directory.
|
||||
name = "the harness reads its queue credential out of the credentials directory";
|
||||
ok =
|
||||
let
|
||||
e = (agentHarness agentQueue).environment;
|
||||
in
|
||||
e.HIVE_AGENT_OIDC_CLIENT_SECRET_FILE == "%d/hive-queue-agent-secret"
|
||||
&& e.HIVE_AGENT_OIDC_CLIENT_ID_FILE == "%d/hive-queue-agent-client-id";
|
||||
}
|
||||
{
|
||||
# An agent built before its hive had a queue. It must declare nothing
|
||||
# rather than name a credential that never arrives — and the harness
|
||||
# then reports "no queue" instead of a half-set environment.
|
||||
name = "an agent with no queue coordinates declares no credential";
|
||||
ok =
|
||||
let
|
||||
u = agentHarness agentNoQueue;
|
||||
in
|
||||
!(u.serviceConfig ? LoadCredential)
|
||||
&& !(u.environment ? HIVE_AGENT_OIDC_CLIENT_SECRET_FILE)
|
||||
&& !(u.environment ? HIVE_AGENT_OIDC_CLIENT_ID_FILE);
|
||||
}
|
||||
{
|
||||
# The doctrine three glue files state, as a property a rewrite has to
|
||||
# keep: a client is defined by holding a certificate the store accepts,
|
||||
|
|
|
|||
Loading…
Reference in a new issue