From dc5c2d8d8389c5a8df819eff81701b80f24cbb53 Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 5 Oct 2026 19:12:46 +0200 Subject: [PATCH] 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 --- nix/host-modules/swarm-authelia-service.nix | 21 +++++++++------- nix/host-modules/swarm-authelia.nix | 25 +++++++------------ .../swarm-victoriametrics-service.nix | 8 +++--- nix/module-eval/swarm-otel-core.nix | 24 ++++++++++++++++++ 4 files changed, 50 insertions(+), 28 deletions(-) diff --git a/nix/host-modules/swarm-authelia-service.nix b/nix/host-modules/swarm-authelia-service.nix index 614ce3a8..ec2c0c41 100644 --- a/nix/host-modules/swarm-authelia-service.nix +++ b/nix/host-modules/swarm-authelia-service.nix @@ -12,8 +12,8 @@ let swarmDomain = config.services.hyperhive.swarm.domain; # The unit name upstream's `services.authelia.instances.` 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. ::: ''; }; diff --git a/nix/host-modules/swarm-authelia.nix b/nix/host-modules/swarm-authelia.nix index 8f551a53..f920babc 100644 --- a/nix/host-modules/swarm-authelia.nix +++ b/nix/host-modules/swarm-authelia.nix @@ -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.` derives the unit, # user, group and StateDirectory from the instance name - # (`authelia` + `-`). 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` + `-`). 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 ]; diff --git a/nix/host-modules/swarm-victoriametrics-service.nix b/nix/host-modules/swarm-victoriametrics-service.nix index afa40511..61a61649 100644 --- a/nix/host-modules/swarm-victoriametrics-service.nix +++ b/nix/host-modules/swarm-victoriametrics-service.nix @@ -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. ''; }; diff --git a/nix/module-eval/swarm-otel-core.nix b/nix/module-eval/swarm-otel-core.nix index 065bfc4f..1d18b372 100644 --- a/nix/module-eval/swarm-otel-core.nix +++ b/nix/module-eval/swarm-otel-core.nix @@ -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