Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/module-eval/ci-metrics.nix
atlas d30cb7d122 keep authelia's oidc client secrets in the store, not on disk
The mint creates each client's secret in the swarm secret store and writes only the digest to disk. It mints only when the store answers and holds none. A new unit moves the plaintext files an earlier release left into the store and removes each once the store holds the same value. The bridge reads its secret from the store into /run before each start. The secret publisher unit and its path unit are gone.
2026-10-30 16:57:58 +01:00

114 lines
5 KiB
Nix

# `checks.module-eval-ci-metrics` — see ./lib.nix for the shared rationale
# (why this suite exists, naming convention, "evaluates not executes"). The CI
# run poller's unit and timer, its default, its CA trust, its collector route
# and its IdP registration; its store grants are in ./bao-grants.nix beside
# every other principal's, and its dashboard in ./grafana.nix.
#
# Every fixture is a shared one, so this cluster evaluates no host of its own.
{
pkgs,
lib,
self,
nixosSystem,
shared,
}:
let
inherit
(import ./lib.nix {
inherit
pkgs
lib
self
nixosSystem
;
})
runGroup
;
bare = shared.bareWithController;
# Every swarm service on one host: the store mints the poller's leaf, so the
# poller defaults on, beside authelia, the publisher and the collector.
allLocal = shared.allLocal;
svc = allLocal.systemd.services.swarm-ci-metrics;
env = svc.environment;
clientId = allLocal.services.hyperhive.swarm.ci-metrics.clientId;
otel = allLocal.services.hyperhive.swarm.otel;
cases = [
{
# The control is the same unit and timer on the all-local host: a
# misspelled attribute path would make the absence arm pass alone.
name = "the CI run poller's unit and timer exist only where its host enables it";
ok =
!(bare.systemd.services ? swarm-ci-metrics)
&& !(bare.systemd.timers ? swarm-ci-metrics)
&& allLocal.services.hyperhive.deploy.ci-metrics.enable
&& svc.serviceConfig.Type == "oneshot"
&& !(svc.serviceConfig.RemainAfterExit or false)
&& allLocal.systemd.timers.swarm-ci-metrics.timerConfig.OnUnitInactiveSec == "5min"
&& lib.elem "timers.target" allLocal.systemd.timers.swarm-ci-metrics.wantedBy;
}
{
# The leaf is handed in as a credential, never an argument; the forge
# token and the client secret are read from the store per pass.
name = "the poller loads only its store leaf, keeps its cursor in its state directory and carries no secret in its unit";
ok =
lib.elem "bao-client-key.pem:/var/lib/swarm-bao-pki/ci-metrics-key.pem" svc.serviceConfig.LoadCredential
&& lib.elem "bao-client.pem:/var/lib/swarm-bao-pki/ci-metrics.pem" svc.serviceConfig.LoadCredential
&& env.BAO_CLIENT_KEY == "/run/credentials/swarm-ci-metrics.service/bao-client-key.pem"
&& svc.serviceConfig.DynamicUser
&& svc.serviceConfig.StateDirectory == "swarm-ci-metrics"
&& !(lib.any (n: lib.hasSuffix "TOKEN" n || lib.hasSuffix "SECRET" n) (lib.attrNames env));
}
{
# The endpoint the poller pushes to is the route the collector admits
# its client on, and that route feeds the swarm tier's metrics pipeline.
name = "the poller pushes to its own collector route, which admits its client into the metrics pipeline";
ok =
let
h = shared.baoWithCollector;
s = h.containers.swarm-otel.config.services.opentelemetry-collector.settings;
in
env.SWARM_CI_METRICS_OTLP_ENDPOINT == "https://${otel.domain}/swarm-ci-metrics"
&& env.SWARM_CI_METRICS_OIDC_CLIENT_ID == clientId
&& otel.serviceProducers.swarm-ci-metrics.clientId == clientId
&& s.receivers."otlp/swarm-ci-metrics".protocols.http.auth.authenticator == "oidc/swarm-ci-metrics"
&& s.extensions."oidc/swarm-ci-metrics".audience == clientId
&& lib.elem "otlp/swarm-ci-metrics" s.service.pipelines."metrics/${otel.producerName}".receivers
&& h.services.nginx.virtualHosts.${otel.domain}.locations ? "/swarm-ci-metrics/";
}
{
# The forge, authelia and the collector chain to the swarm's own CA;
# without the bundle every pass fails `UnknownIssuer`. hive-c0re's
# bundle on the bare host is the control that the absence arm looks up
# a unit name the module would write.
name = "the poller trusts the swarm's CA through its own bundle, which exists only where the poller does";
ok =
allLocal.services.hyperhive.gateway.useSelfSigned
&& (env.SSL_CERT_FILE or null) == "/run/swarm-ci-metrics-ca/trust-bundle.pem"
&& lib.elem "swarm-ci-metrics-ca-bundle.service" svc.requires
&& lib.elem "hive-tls-ca.service" (
allLocal.systemd.services.swarm-ci-metrics-ca-bundle.requires or [ ]
)
&& bare.systemd.services ? hive-c0re-ca-bundle
&& !(bare.systemd.services ? swarm-ci-metrics-ca-bundle);
}
{
name = "the poller's client is registered as a machine, and the mint stores its secret";
ok =
let
c = lib.findFirst (
c: c.id == clientId
) null allLocal.services.hyperhive.swarm.authelia.oidc.clients;
in
c != null
&& c.kind == "machine"
&& c.audience == [ clientId ]
&& lib.hasInfix "secret/swarm/services/${clientId}/oidc/client" (
allLocal.containers.swarm-authelia.config.systemd.services.authelia-swarm-secrets.script
);
}
];
in
runGroup "ci-metrics" cases