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
97
nix/agent-modules/queue.nix
Normal file
97
nix/agent-modules/queue.nix
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Swarm-queue coordinates for this agent's harness.
|
||||
#
|
||||
# Three of the four the harness needs are addresses (this file's two options
|
||||
# plus the secret's path); the fourth, the client id, arrives as a file beside
|
||||
# the secret so a reader never spells `hive-<name>-agent` a second time.
|
||||
#
|
||||
# ⚠️ The credential arrives as a systemd credential and NOT as a bind mount,
|
||||
# and the mode is why: the host file is `root:0600` and this unit runs as the
|
||||
# unprivileged agent user. nspawn's `--load-credential` (written by
|
||||
# `hive_c0re::lifecycle::host_config`) is read by the container manager as
|
||||
# root and re-exposed under this unit's own `User=`; a bind would deliver a
|
||||
# file the harness cannot open.
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
cfg = config.hyperhive.queue;
|
||||
configured = cfg.natsUrl != null && cfg.tokenEndpoint != null;
|
||||
|
||||
# The two ids `hive_c0re::lifecycle::host_config` forwards under. Neither
|
||||
# side can discover the other's spelling, so a rename is a rename there too.
|
||||
secretCredential = "hive-queue-agent-secret";
|
||||
clientIdCredential = "hive-queue-agent-client-id";
|
||||
in
|
||||
{
|
||||
options.hyperhive.queue = {
|
||||
natsUrl = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "nats://10.42.0.1:4222";
|
||||
description = ''
|
||||
Where the swarm queue listens, as this container reaches it.
|
||||
|
||||
Set by the generated meta flake from the host's
|
||||
{option}`services.hyperhive.deploy.hive-controller.queue.agentNatsUrl`,
|
||||
which is the bridge address rather than a loopback one — inside this
|
||||
container `127.0.0.1` is the agent itself.
|
||||
|
||||
`null` means this hive has no queue, and the harness then declares no
|
||||
credential and logs that it has none. It is deliberately not defaulted
|
||||
to anything: a guessed address builds fine and talks to the wrong
|
||||
machine.
|
||||
'';
|
||||
};
|
||||
|
||||
tokenEndpoint = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "https://auth.example.com/api/oidc/token";
|
||||
description = ''
|
||||
The swarm IdP's OAuth2 token endpoint. The harness mints a
|
||||
`client_credentials` token there and presents it to the queue, which
|
||||
authenticates it as the client named in the delivered credential.
|
||||
|
||||
Set together with {option}`hyperhive.queue.natsUrl` or not at all —
|
||||
the harness treats a half-set pair as a deployment bug rather than as
|
||||
"no queue".
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf configured {
|
||||
systemd.services.hive-agent = {
|
||||
# Bare ids, no paths: this is the terse `LoadCredential=` form that
|
||||
# inherits a credential the service *manager* received, which is what
|
||||
# the container manager passed in. `man systemd.exec` also makes that
|
||||
# form non-fatal when the credential is absent, which is exactly the
|
||||
# behaviour a hive whose publisher has not run yet needs — the unit
|
||||
# starts, finds no id, and says so.
|
||||
serviceConfig.LoadCredential = [
|
||||
secretCredential
|
||||
clientIdCredential
|
||||
];
|
||||
environment = {
|
||||
# `%d` is `$CREDENTIALS_DIRECTORY`, per-unit and owned by `User=`.
|
||||
# Same shape hive-c0re's own queue client is handed its secret in
|
||||
# (`nix/host-modules/hive-c0re/environment.nix`) — the harness reads
|
||||
# a path and never a value.
|
||||
HIVE_AGENT_OIDC_CLIENT_SECRET_FILE = "%d/${secretCredential}";
|
||||
# The id is public (it is sent to the token endpoint on every
|
||||
# connection) but still arrives as a path, because it arrives *with*
|
||||
# the secret. `QueueConfig::from_env` wants it as a value, so the
|
||||
# harness reads this file itself — see `hive-agent`'s `swarm_queue`.
|
||||
HIVE_AGENT_OIDC_CLIENT_ID_FILE = "%d/${clientIdCredential}";
|
||||
};
|
||||
# No `HIVE_AGENT_OIDC_CA_FILE`. The hive's own client needs one because
|
||||
# the host does not trust the swarm's CA, but an agent does: the meta
|
||||
# flake embeds the hive CA and the swarm root it is issued under into
|
||||
# this container's `security.pki.certificateFiles` at build time, and
|
||||
# reqwest's rustls backend verifies against the system store. A path
|
||||
# here would need the bundle delivered as a third credential to say
|
||||
# nothing new.
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue