Watch
0
0
Fork
You've already forked hyperhive
0

nix: follow-ups from the service/deploy split

- swarm.victoriametrics.port: the description states why the listener is
  loopback-only instead of pointing "below" at text in another file.
- authelia: the deploy half derives its unit and instance names from
  swarm.authelia.unit, so the instance is spelled once, in
  swarm-authelia-service.nix.
- authelia: the tokenEndpointAuthMethod and bearerAuthz descriptions name
  the obligations the authelia host asserts instead of "below"; the
  swarm-authelia.nix header describes that file only.
- module-eval-swarm-otel-core: swarm.otel.audience must contain both store
  push URLs the collector's exporters use and request tokens for.

Refs #3742
This commit is contained in:
atlas 2026-10-05 19:12:46 +02:00
commit dc5c2d8d83
4 changed files with 50 additions and 28 deletions

View file

@ -12,8 +12,8 @@ let
swarmDomain = config.services.hyperhive.swarm.domain;
# The unit name upstream's `services.authelia.instances.<name>` derives,
# which `unit` publishes. ./swarm-authelia.nix binds the same two names for
# the instance it declares, and the two must agree.
# which `unit` publishes. ./swarm-authelia.nix reads its instance name back
# from `unit`, so this is the one place the instance is spelled.
instance = "swarm";
unitName = "authelia-${instance}";
in
@ -240,9 +240,10 @@ in
method must be *stated*: omitting it is refused with
`must be configured as 'client_secret_basic', … but it's
configured as` an empty string. The sentence above is
true of an ordinary client and false of that one, which
is exactly how a reviewer reads this option and concludes
the assertion below is wrong.
true of an ordinary client and false of that one: the
host that runs authelia asserts at evaluation that a
`bearerAuthz` client states `client_secret_basic`,
`client_secret_jwt` or `private_key_jwt`.
'';
};
@ -290,12 +291,14 @@ in
one value that is legal here and nothing else.
::: {.note}
Setting this obliges two other options, and the assertions
below enforce it. Authelia checks the same thing, but only
Setting this obliges two other options: a non-empty
`audience`, and a `tokenEndpointAuthMethod` of
`client_secret_basic`, `client_secret_jwt` or
`private_key_jwt`. Authelia checks the same thing, but only
in its `preStart` validator — which means a violation
builds and deploys cleanly and then fails to restart,
taking swarm SSO down. The assertions move that to
evaluation, where a wrong value costs nothing.
taking swarm SSO down. The host that runs authelia asserts
both at evaluation, where a wrong value costs nothing.
:::
'';
};

View file

@ -1,13 +1,8 @@
# The swarm's SSO provider: one authelia for the whole swarm, in a
# `swarm-authelia` nixos-container.
#
# Two halves, and only one of them is conditional:
#
# - the CLIENT pointer (`url`) exists on every hive, because a hive
# that doesn't run authelia still has to know where to send people.
# - the CONTAINER only exists where the swarm's shared services live.
# `deploy.allSwarmServices` asserts this module's `enable`
# (see ./swarm-required-services.nix); a hive is a client by default.
# `swarm-authelia` nixos-container on the host with `deploy.authelia.enable`
# (`deploy.allSwarmServices` defaults it on, see
# ./swarm-required-services.nix). What every hive sees of it, `url` and the
# OIDC client register included, is in ./swarm-authelia-service.nix.
#
# The file backend is the right shape, not a placeholder for LDAP: what
# makes a directory necessary is the size of the subject set, and this
@ -91,12 +86,10 @@ let
# Upstream's `services.authelia.instances.<name>` derives the unit,
# user, group and StateDirectory from the instance name
# (`authelia` + `-<name>`). Naming them here rather than repeating the
# literal keeps the generator unit below and the module in step.
# ./swarm-authelia-service.nix binds the same two names for `unit`;
# the two must agree.
instance = "swarm";
unitName = "authelia-${instance}";
# (`authelia` + `-<name>`). Both are read back from `swarm.authelia.unit`,
# so the instance declared below is the one that option names.
unitName = lib.removeSuffix ".service" cfg.unit;
instance = lib.removePrefix "authelia-" unitName;
stateDir = "/var/lib/${unitName}";
caTrust = import ./lib/hive-ca-trust.nix {
@ -790,7 +783,7 @@ in
../container-modules/swarm-container.nix
(import ../container-modules/swarm-container-resolver.nix {
inherit (networkCfg) bridgeIp;
dnsConsumers = [ "authelia-${instance}.service" ];
dnsConsumers = [ cfg.unit ];
})
caBundleModule
];

View file

@ -47,9 +47,11 @@ in
type = lib.types.port;
default = 8428;
description = ''
Port VictoriaMetrics listens on, bound to loopback only (see
below). Upstream's own default, kept so an operator reading
VictoriaMetrics documentation finds what they expect.
Port VictoriaMetrics listens on, bound to loopback only on the
host that runs it: its write endpoint is unauthenticated, so the
gateway's vhost there is the only route to it. Upstream's own
default, kept so an operator reading VictoriaMetrics
documentation finds what they expect.
'';
};

View file

@ -139,6 +139,30 @@ let
&& lib.hasInfix "metrics.t.local" m
&& lib.hasInfix "logs.t.local" l;
}
{
# `swarm.otel.audience` is built in ../host-modules/swarm-otel-service.nix,
# the push URLs the exporters use in ../host-modules/swarm-otel.nix. A URL
# the registration lacks is refused at the token endpoint with
# `invalid_target`, so every push fails while the collector reports
# healthy.
name = "the collector's registered audiences include both store push URLs it requests";
ok =
let
s = otelSettings otelBaoWithAuthelia;
registered = otelBaoWithAuthelia.services.hyperhive.swarm.otel.audience;
withoutQuery = url: lib.head (lib.splitString "?" url);
pushUrls = [
s.exporters."otlphttp/victoriametrics".metrics_endpoint
(withoutQuery s.exporters."otlphttp/victorialogs".logs_endpoint)
];
requested = map (n: s.extensions."oauth2client/${n}".endpoint_params.audience) [
"victoriametrics"
"victorialogs"
];
in
lib.all (u: builtins.elem u registered) pushUrls
&& lib.sort lib.lessThan requested == lib.sort lib.lessThan pushUrls;
}
{
# `_HOSTNAME` cannot separate machines on its own: a hostname is a
# config value two of them can share, and then every stream for a unit