Watch
0
0
Fork
You've already forked hyperhive
0

swarm-builder, swarm-claude-login, swarm-review-classifier: forward each container's journal to the swarm collector

Each container now runs the journal forwarder the queue's container runs,
pushing to a receiver of its own on the swarm collector as the service's
existing OIDC client, with the client's secret read inside the container
by the fetch unit. Their store identities are system credentials, so the
fetch unit loads them too. The three containers drop --link-journal=host,
which hid their journals from the forwarder, and the host collector stops
reading them.
This commit is contained in:
atlas 2026-10-16 03:28:23 +02:00
commit a2a94f63c8
10 changed files with 321 additions and 32 deletions

View file

@ -94,6 +94,92 @@ let
# The same swarm's IdP host, for the arm that the two agree.
autheliaColocated = shared.autheliaOnly;
# The other three queue clients with a container of their own, each forwarding
# its journal as the queue's does. One shared host: the switch that turns on
# every swarm service, which other suites already evaluate.
servicesHost = shared.swarmServicesWithController;
forwarderServices = [
{
machine = "swarm-builder";
name = "builder";
credential = "builder-bao";
port = 8892;
}
{
machine = "swarm-claude-login";
name = "claude-login";
credential = "claude-login-bao";
port = 8893;
}
{
machine = "swarm-review-classifier";
name = "review-classifier";
credential = "review-classifier-bao";
port = 8894;
}
];
forwarderFetchDir = "/run/credentials/swarm-journal-forwarder-oidc.service";
forwarderCases = lib.concatMap (
svc:
let
h = servicesHost;
container = h.containers.${svc.machine};
c = container.config;
s = c.services.opentelemetry-collector.settings;
otel = h.services.hyperhive.swarm.otel;
fetch = c.systemd.services.swarm-journal-forwarder-oidc;
clientId = h.services.hyperhive.swarm.${svc.name}.clientId;
in
[
{
# The container is on the bridge, so its identity is a system credential
# rather than a file: the fetch unit loads it, and reads the same client
# secret the daemon's own identity is granted.
name = "the ${svc.name} container forwards its journal to its own route on the swarm collector";
ok =
c.services.opentelemetry-collector.enable
&& s.exporters.otlphttp.endpoint == "https://${otel.domain}/${svc.machine}"
&& s.exporters.otlphttp.auth.authenticator == "oauth2client/swarm"
&& s.extensions."oauth2client/swarm".client_id == clientId
&& s.extensions."oauth2client/swarm".endpoint_params.audience == clientId
&& lib.elem "oauth2client/swarm" s.service.extensions
&& s.service.pipelines.logs.receivers == [ "journald" ]
&&
(lib.findFirst (a: a.key == "service.name") null s.processors.resource.attributes).value == svc.name
&& lib.elem "oidc-client-secret:/run/swarm-journal-forwarder-oidc/client-secret" c.systemd.services.opentelemetry-collector.serviceConfig.LoadCredential
&& fetch.environment.BAO_CLIENT_CERT == "${forwarderFetchDir}/${svc.credential}-cert"
&& fetch.environment.BAO_CLIENT_KEY == "${forwarderFetchDir}/${svc.credential}-key"
&& lib.elem "${svc.credential}-cert" fetch.serviceConfig.LoadCredential
&& lib.elem "${svc.credential}-key" fetch.serviceConfig.LoadCredential
&& lib.hasInfix "bao kv get -field=value secret/swarm/services/${clientId}/oidc/client" fetch.script
# The guest keeps its journal; a host-linked one is empty inside. The
# leaf's credential flag is what remains.
&& !(lib.elem "--link-journal=host" container.extraFlags)
&& lib.any (lib.hasPrefix "--load-credential=${svc.credential}-cert:") container.extraFlags;
}
{
name = "the ${svc.name} forwarder's metrics port is its own and loopback";
ok =
let
# Every other collector port on the host, and the queue's forwarder.
taken = [
8888
8889
8890
h.services.hyperhive.swarm.nats.otel.telemetryPort
]
++ map (o: o.port) (lib.filter (o: o.machine != svc.machine) forwarderServices);
reader = lib.head s.service.telemetry.metrics.readers;
in
c.services.hyperhive.swarmJournalForwarder.telemetryPort == svc.port
&& !(lib.elem svc.port taken)
&& reader.pull.exporter.prometheus.host == "127.0.0.1"
&& reader.pull.exporter.prometheus.port == svc.port;
}
]
) forwarderServices;
cases = [
{
name = "authelia and nats declare no scrape target on a host with no collector";
@ -633,6 +719,51 @@ let
in
c != null && c.audience == [ "swarm-nats" ] && c.accessTokenSignedResponseAlg == "RS256";
}
];
{
# The far end for the three: a receiver, authenticator, pipeline entry and
# gateway route each, checking that service's client as audience, beside
# the queue's, on a collector host that runs none of them.
name = "the swarm collector admits the builder, claude login and classifier forwarders on receivers of their own";
ok =
let
h = shared.baoWithCollector;
otel = h.services.hyperhive.swarm.otel;
s = h.containers.swarm-otel.config.services.opentelemetry-collector.settings;
clientOf = svc: h.services.hyperhive.swarm.${svc.name}.clientId;
ports = map (svc: otel.serviceProducers.${svc.machine}.port) forwarderServices;
in
!h.services.hyperhive.deploy.builder.enable
&& lib.all (
svc:
s.receivers."otlp/${svc.machine}".protocols.http.auth.authenticator == "oidc/${svc.machine}"
&& s.extensions."oidc/${svc.machine}".audience == clientOf svc
&& lib.elem "oidc/${svc.machine}" s.service.extensions
&& lib.elem "otlp/${svc.machine}" s.service.pipelines."logs/${otel.producerName}".receivers
&& h.services.nginx.virtualHosts.${otel.domain}.locations ? "/${svc.machine}/"
) forwarderServices
# Four producers' receivers on the collector's host, none sharing a port.
&& lib.length (lib.unique (ports ++ [ otel.serviceProducers.swarm-nats.port ])) == 4;
}
{
# The token each forwarder mints must name its own client as audience and
# be a JWT, or the receiver refuses it.
name = "the builder, claude login and classifier clients are registered with their own audience and a signed access token";
ok =
let
clients = shared.autheliaOnly.services.hyperhive.swarm.authelia.oidc.clients;
ok1 =
id:
let
c = lib.findFirst (c: c.id == id) null clients;
in
c != null
&& c.audience == [ id ]
&& c.accessTokenSignedResponseAlg == "RS256"
&& c.kind == "machine";
in
lib.all ok1 (map (svc: svc.machine) forwarderServices);
}
]
++ forwarderCases;
in
runGroup "nats-authelia" cases