Every agent now connects to the queue and reads logs with its own secret from the store (#5181), so the per-hive `hive-<h>-agent` OIDC client has no reader left. Remove it end to end: - hive-c0re no longer forwards `hive-queue-agent-{secret,client-id}` into containers, and no longer forwards HIVE_AGENT_OIDC_TOKEN_ENDPOINT; the agent module that loaded them (nix/agent-modules/queue.nix) is gone, its options refused with a removal message. - The host reader (glue-queue-agent-credential.nix, swarm-bao-queue-agent), its store role and policy unit (swarm-bao-queue-agent-policy), its PKI leaf and the deploy.bao.queueAgent* / queue.agentCredentialDir options are gone; the options are refused with a removal message. - authelia mints no `hive-<h>-agent` client, and swarm-secret-publish no longer writes swarm/hives/<h>/queue/agent. - swarm-secret-client drops queue::agent_client_path / queue::Credential. - HIVE_AGENT_NATS_URL is now gated on the agents' queue address alone. The log store's authelia check stays for operators. swarm-nats-auth still refuses a `-agent` client id; that refusal and `agentClientSuffix` are left for a follow-up. Refs #4459 Refs #4765
178 lines
8.1 KiB
Nix
178 lines
8.1 KiB
Nix
# What a host decides about the swarm's secret publisher: whether it runs, and
|
||
# the store identity it presents, plus the list of secrets it copies. The unit
|
||
# itself is ../container-modules/swarm-secret-publisher.nix, inside authelia's
|
||
# container beside the mint whose secrets it copies; ./swarm-authelia.nix
|
||
# imports it there and hands the identity in as system credentials.
|
||
{
|
||
lib,
|
||
config,
|
||
...
|
||
}:
|
||
let
|
||
hyperhiveCfg = config.services.hyperhive;
|
||
hiveNames = lib.attrNames hyperhiveCfg.swarm.hives;
|
||
|
||
# The hive's own client id, which hive-c0re publishes the hive's status with.
|
||
# Composed exactly as ./swarm-authelia.nix composes it, from the same
|
||
# read-only option, so a rename there cannot leave this spelling behind.
|
||
hiveClientId = hive: "${hyperhiveCfg.swarm.authelia.hiveClientPrefix}${hive}";
|
||
|
||
# The swarm's own services, as opposed to its hives. One client for the whole
|
||
# swarm rather than one per hive, so one value in the store rather than a copy
|
||
# each: `swarm/services/<id>/oidc/client`.
|
||
#
|
||
# The ids come from `swarm.*`, which is identical on every host — that is what
|
||
# lets this host name a service's client while running none of them, and it is
|
||
# the same read each service's own module registers its client with. A swarm
|
||
# that runs no Grafana, or no collector, mints no secret for the one it lacks,
|
||
# so its entry skips below rather than needing a condition here.
|
||
serviceClientIds = [
|
||
hyperhiveCfg.swarm.grafana.oidc.clientId
|
||
hyperhiveCfg.swarm.otel.clientId
|
||
# The secret store's own journal forwarder. A third swarm service by this
|
||
# list's own rule — a client registered once for the whole swarm, whose
|
||
# secret one host mints and another reads — even though the principal
|
||
# behind it runs inside the store's container rather than beside it.
|
||
# Missing from here, authelia mints the value and nothing carries it, so
|
||
# the reader waits on a path that is never written.
|
||
hyperhiveCfg.swarm.bao.otel.clientId
|
||
# The store's browser UI, whose `oidc` auth method the store's host
|
||
# configures with this secret.
|
||
hyperhiveCfg.swarm.bao.ui.oidc.clientId
|
||
# The queue's auth-callout responder, which reads it itself on the
|
||
# queue's host.
|
||
hyperhiveCfg.swarm.nats.clientId
|
||
# The homeserver, read by `swarm-bao-matrix-oidc` on its host.
|
||
hyperhiveCfg.swarm.matrix.sso.clientId
|
||
# The forge's login source, read by `swarm-bao-forge-oidc` on its host.
|
||
hyperhiveCfg.swarm.forge.sso.clientId
|
||
# The review classifier, which reads it itself inside its container.
|
||
hyperhiveCfg.swarm.review-classifier.clientId
|
||
# The swarm builder, which reads it itself inside its container.
|
||
hyperhiveCfg.swarm.builder.clientId
|
||
# The claude login minter, which reads it itself inside its container.
|
||
hyperhiveCfg.swarm.claude-login.clientId
|
||
# The notification router, which reads it itself inside its container.
|
||
hyperhiveCfg.swarm.notify-router.clientId
|
||
# The journal forwarders in the CI runner's, Grafana's, the log store's,
|
||
# the forge's and the homeserver's containers, each reading it inside its
|
||
# container.
|
||
hyperhiveCfg.swarm.ci-runner.otel.clientId
|
||
hyperhiveCfg.swarm.grafana.otel.clientId
|
||
hyperhiveCfg.swarm.victorialogs.otel.clientId
|
||
hyperhiveCfg.swarm.forge.otel.clientId
|
||
hyperhiveCfg.swarm.matrix.otel.clientId
|
||
# The metrics store container's journal forwarder, whose host reads it.
|
||
hyperhiveCfg.swarm.victoriametrics.otel.clientId
|
||
# The CI run poller, which reads it itself on its host.
|
||
hyperhiveCfg.swarm.ci-metrics.clientId
|
||
# Authelia's container's journal forwarder, which reads it back under the
|
||
# publisher's own identity.
|
||
hyperhiveCfg.swarm.authelia.otel.clientId
|
||
];
|
||
|
||
# The swarm controller's OIDC client, which it reads back from the store
|
||
# under its own certificate.
|
||
controllerClientId = hyperhiveCfg.swarm.controller.queueClientId;
|
||
controllerSecretPath = "secret/swarm/controller/swarm-controller/oidc/client";
|
||
|
||
secrets =
|
||
# Each hive's own client, which that hive's hive-c0re reads under its own
|
||
# certificate. The nix half of
|
||
# `swarm_secret_client::queue::hive_client_path`.
|
||
map (hive: {
|
||
clientId = hiveClientId hive;
|
||
path = "secret/swarm/hives/${hive}/oidc/client";
|
||
}) hiveNames
|
||
# A service's client id is the swarm-wide option both ends already read.
|
||
++ map (id: {
|
||
clientId = id;
|
||
path = "secret/swarm/services/${id}/oidc/client";
|
||
}) serviceClientIds
|
||
# Under `controller/`, which no hive reads: this client may read every
|
||
# hive's status. The nix half of
|
||
# `swarm_secret_client::queue::controller_client_path`.
|
||
++ [
|
||
{
|
||
clientId = controllerClientId;
|
||
path = controllerSecretPath;
|
||
}
|
||
];
|
||
in
|
||
{
|
||
options.services.hyperhive.deploy.swarm-secret-publisher = {
|
||
enable = lib.mkOption {
|
||
type = lib.types.bool;
|
||
default = false;
|
||
description = ''
|
||
Publish the OIDC client secrets this host mints into the swarm's
|
||
secret store, so a hive that does not run authelia, a swarm service
|
||
or the controller can read its own — from wherever it runs, this host
|
||
included. The unit, `swarm-secret-publish`, runs inside authelia's
|
||
container, so it exists only where authelia runs.
|
||
|
||
Defaults to whether this host mints the OIDC secrets (set by
|
||
`glue-secret-publisher-authelia.nix`), which is the only half of the
|
||
question that is a property of *this* host.
|
||
|
||
⚠️ Deliberately **not** `deploy.bao.enable`. That asks whether the
|
||
store stands here, and a publisher beside a remote store, holding a
|
||
leaf issued out of band, is a deployment this exists to serve —
|
||
defaulting on co-location would leave exactly that shape silently
|
||
publishing nothing. Whether the wiring is complete is the client
|
||
identity's job (see `baoClientCertFile`), not this option's.
|
||
|
||
Turning it off leaves every hive unable to publish its status, the
|
||
swarm controller unable to start, the swarm's Grafana without any login
|
||
at all, its collector pushing unauthenticated, the queue's responder
|
||
unable to start, the homeserver unable to start, and the forge's login
|
||
source without its secret.
|
||
Each secret has exactly one route and this is the producer's
|
||
end of it, so the honest reason to set it false is a deployment
|
||
delivering those secrets by some other mechanism it owns.
|
||
'';
|
||
};
|
||
|
||
baoClientCertFile = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
default = null;
|
||
description = ''
|
||
Client certificate this publisher presents to the store. Its subject
|
||
must be {option}`services.hyperhive.deploy.bao.secretPublisherCommonName`
|
||
— cert auth matches on the CN, and the role accepts nothing else.
|
||
|
||
A path on the host. ./swarm-authelia.nix hands it into authelia's
|
||
container as an nspawn system credential, and nspawn refuses to
|
||
start the container while a credential's source is missing: set,
|
||
the file has to exist before authelia's container starts.
|
||
|
||
No default: a module that guessed would be holding the CA opinion
|
||
./swarm-bao.nix deliberately does not hold.
|
||
./glue-secret-publisher-bao-identity.nix points it at the leaf
|
||
./glue-bao-tls.nix mints, where this host mints one.
|
||
'';
|
||
};
|
||
|
||
baoClientKeyFile = lib.mkOption {
|
||
type = lib.types.nullOr lib.types.str;
|
||
default = null;
|
||
description = ''
|
||
Private key for {option}`services.hyperhive.deploy.swarm-secret-publisher.baoClientCertFile`.
|
||
Both or neither — the unit does not exist unless each is set. A host
|
||
path, handed in like the certificate.
|
||
'';
|
||
};
|
||
|
||
secrets = lib.mkOption {
|
||
type = lib.types.listOf lib.types.attrs;
|
||
readOnly = true;
|
||
internal = true;
|
||
default = secrets;
|
||
description = ''
|
||
Every secret the publisher copies: the minted client's id and the store
|
||
path its reader fetches. Derived from `swarm.*` alone, so it is the same list on every
|
||
host; ./swarm-authelia.nix hands it to the unit in its container.
|
||
'';
|
||
};
|
||
};
|
||
}
|