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:
parent
40a2bd047b
commit
a2a94f63c8
10 changed files with 321 additions and 32 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue