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; swarmDomain = config.services.hyperhive.swarm.domain;
# The unit name upstream's `services.authelia.instances.<name>` derives, # The unit name upstream's `services.authelia.instances.<name>` derives,
# which `unit` publishes. ./swarm-authelia.nix binds the same two names for # which `unit` publishes. ./swarm-authelia.nix reads its instance name back
# the instance it declares, and the two must agree. # from `unit`, so this is the one place the instance is spelled.
instance = "swarm"; instance = "swarm";
unitName = "authelia-${instance}"; unitName = "authelia-${instance}";
in in
@ -240,9 +240,10 @@ in
method must be *stated*: omitting it is refused with method must be *stated*: omitting it is refused with
`must be configured as 'client_secret_basic', … but it's `must be configured as 'client_secret_basic', … but it's
configured as` an empty string. The sentence above is configured as` an empty string. The sentence above is
true of an ordinary client and false of that one, which true of an ordinary client and false of that one: the
is exactly how a reviewer reads this option and concludes host that runs authelia asserts at evaluation that a
the assertion below is wrong. `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. one value that is legal here and nothing else.
::: {.note} ::: {.note}
Setting this obliges two other options, and the assertions Setting this obliges two other options: a non-empty
below enforce it. Authelia checks the same thing, but only `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 in its `preStart` validator — which means a violation
builds and deploys cleanly and then fails to restart, builds and deploys cleanly and then fails to restart,
taking swarm SSO down. The assertions move that to taking swarm SSO down. The host that runs authelia asserts
evaluation, where a wrong value costs nothing. 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 # The swarm's SSO provider: one authelia for the whole swarm, in a
# `swarm-authelia` nixos-container. # `swarm-authelia` nixos-container on the host with `deploy.authelia.enable`
# # (`deploy.allSwarmServices` defaults it on, see
# Two halves, and only one of them is conditional: # ./swarm-required-services.nix). What every hive sees of it, `url` and the
# # OIDC client register included, is in ./swarm-authelia-service.nix.
# - 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.
# #
# The file backend is the right shape, not a placeholder for LDAP: what # 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 # 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, # Upstream's `services.authelia.instances.<name>` derives the unit,
# user, group and StateDirectory from the instance name # user, group and StateDirectory from the instance name
# (`authelia` + `-<name>`). Naming them here rather than repeating the # (`authelia` + `-<name>`). Both are read back from `swarm.authelia.unit`,
# literal keeps the generator unit below and the module in step. # so the instance declared below is the one that option names.
# ./swarm-authelia-service.nix binds the same two names for `unit`; unitName = lib.removeSuffix ".service" cfg.unit;
# the two must agree. instance = lib.removePrefix "authelia-" unitName;
instance = "swarm";
unitName = "authelia-${instance}";
stateDir = "/var/lib/${unitName}"; stateDir = "/var/lib/${unitName}";
caTrust = import ./lib/hive-ca-trust.nix { caTrust = import ./lib/hive-ca-trust.nix {
@ -790,7 +783,7 @@ in
../container-modules/swarm-container.nix ../container-modules/swarm-container.nix
(import ../container-modules/swarm-container-resolver.nix { (import ../container-modules/swarm-container-resolver.nix {
inherit (networkCfg) bridgeIp; inherit (networkCfg) bridgeIp;
dnsConsumers = [ "authelia-${instance}.service" ]; dnsConsumers = [ cfg.unit ];
}) })
caBundleModule caBundleModule
]; ];

View file

@ -47,9 +47,11 @@ in
type = lib.types.port; type = lib.types.port;
default = 8428; default = 8428;
description = '' description = ''
Port VictoriaMetrics listens on, bound to loopback only (see Port VictoriaMetrics listens on, bound to loopback only on the
below). Upstream's own default, kept so an operator reading host that runs it: its write endpoint is unauthenticated, so the
VictoriaMetrics documentation finds what they expect. 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 "metrics.t.local" m
&& lib.hasInfix "logs.t.local" l; && 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 # `_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 # config value two of them can share, and then every stream for a unit