nix: make swarm.authelia.url non-nullable, trim its docs
Review response on #4620: not having SSO is not a supported deployment, so the type should not permit it, and the docs paragraph explaining why SSO is always present is redundant once the type says so. - swarm.authelia.url drops types.nullOr. - Every consumer's null-arm is gone: two option defaults (swarm-controller's and swarm's own statusPublish.tokenEndpoint) that produced an empty/null placeholder when the URL was null now unconditionally compute the real derived URL. Five now-dead "assertion = ... != null" guards (swarm-authelia's bridge, swarm-grafana, swarm-otel, swarm-nats, hive-forge, hive-matrix) are removed as unreachable — in every case the same URL was already interpolated unconditionally a few lines below the guard. - grafanaNoSso, the module-eval fixture whose sole purpose was exercising the now-unsupported no-IdP refusal, is removed along with its dedicated test case; swarm.authelia.url = null is a type error now, not a value that reaches that assertion. - docs/swarm/services.md: cut the clause about setting the option to null and the sentence explaining why the URL is co-location- independent — both redundant now that the type enforces it.
This commit is contained in:
parent
4b6214305f
commit
5ec0ce90fd
10 changed files with 21 additions and 154 deletions
|
|
@ -69,13 +69,11 @@ provider, differentiated by roles and claims rather than by mechanism —
|
|||
there is one IdP and one auth path.
|
||||
|
||||
- **`deploy.authelia`** — run the container here.
|
||||
- **`swarm.authelia.url`** — where clients go to authenticate.
|
||||
Present on **every** hive and the same value on all of them:
|
||||
- **`swarm.authelia.url`** — where clients go to authenticate. Present
|
||||
on **every** hive and the same value on all of them:
|
||||
`https://<swarm.authelia.domain>`, whether or not this host runs the
|
||||
container. The name is what a client is given; resolution decides which
|
||||
address it reaches. Set it explicitly when joining a swarm whose IdP is
|
||||
under another name, or to `null` to say the swarm has no SSO — consumers
|
||||
then refuse rather than guessing an address.
|
||||
container. Set it explicitly when joining a swarm whose IdP is under
|
||||
another name.
|
||||
|
||||
swarm-controller writes the users database, not by hand: hive-c0re
|
||||
creates and destroys agents continuously, so the subject set is dynamic.
|
||||
|
|
|
|||
|
|
@ -573,20 +573,6 @@ in
|
|||
host: see docs/swarm/ for which secret goes where.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# Without a provider URL there is nothing to discover against,
|
||||
# and the rendered unit would ask `null/.well-known/…`.
|
||||
assertion = autheliaUrl != null;
|
||||
message = ''
|
||||
The forge's SSO login source requires
|
||||
services.hyperhive.swarm.authelia.url — the base URL of the
|
||||
swarm's SSO provider.
|
||||
|
||||
It defaults to this host's own instance only when this host
|
||||
runs authelia. A hive that federates with a swarm sets it
|
||||
explicitly to wherever that provider lives.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.rootUrl == null || lib.hasSuffix "/" cfg.rootUrl;
|
||||
message = ''
|
||||
|
|
|
|||
|
|
@ -968,20 +968,6 @@ in
|
|||
host: see docs/swarm/ for which secret goes where.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# Without a provider URL there is nothing to discover against, and
|
||||
# the rendered config would name `null` as its issuer.
|
||||
assertion = autheliaUrl != null;
|
||||
message = ''
|
||||
This homeserver's SSO login flow requires
|
||||
services.hyperhive.swarm.authelia.url — the base URL of the
|
||||
swarm's SSO provider.
|
||||
|
||||
It defaults to this host's own instance only when this host
|
||||
runs authelia. A hive that federates with a swarm sets it
|
||||
explicitly to wherever that provider lives.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# The callback URL must name the homeserver itself, and with no
|
||||
# gateway vhost there is no public name for it to be built from.
|
||||
|
|
|
|||
|
|
@ -457,7 +457,7 @@ in
|
|||
};
|
||||
|
||||
url = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
type = lib.types.str;
|
||||
default = "https://${cfg.domain}";
|
||||
defaultText = lib.literalExpression ''"https://''${domain}"'';
|
||||
example = "https://auth.example.com";
|
||||
|
|
@ -474,9 +474,8 @@ in
|
|||
wrong, the same way {option}`services.hyperhive.swarm.otel.domain`
|
||||
has none.
|
||||
|
||||
Still nullable, for a deployment that has to say "this swarm has
|
||||
no IdP" explicitly; consumers refuse rather than invent an
|
||||
address when it is null.
|
||||
Not having SSO is not a supported deployment: every swarm has an
|
||||
IdP, so this is never `null`.
|
||||
'';
|
||||
};
|
||||
|
||||
|
|
@ -953,18 +952,6 @@ in
|
|||
# would reject it, but three layers away and at boot — naming both
|
||||
# sources here is the cheaper failure.
|
||||
++ [
|
||||
{
|
||||
# The bridge introspects by name, so a null URL becomes a nix
|
||||
# coercion error several files from its cause. Only reachable by
|
||||
# enabling authelia and clearing `url` by hand — an assertion
|
||||
# rather than a fallback, because a guessed URL that evaluates
|
||||
# cleanly is worse than a refused build.
|
||||
assertion = cfg.url != null;
|
||||
message =
|
||||
"services.hyperhive.swarm.authelia.url must not be null when authelia "
|
||||
+ "is enabled: swarm-authelia-bridge introspects at "
|
||||
+ "`\${url}/api/oidc/introspection` from inside its container.";
|
||||
}
|
||||
{
|
||||
assertion = lib.length (lib.unique (map (c: c.id) cfg.oidc.clients)) == lib.length cfg.oidc.clients;
|
||||
message =
|
||||
|
|
|
|||
|
|
@ -382,7 +382,7 @@ in
|
|||
|
||||
tokenEndpoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = lib.optionalString (autheliaCfg.url != null) "${autheliaCfg.url}/api/oidc/token";
|
||||
default = "${autheliaCfg.url}/api/oidc/token";
|
||||
defaultText = lib.literalExpression ''"''${swarm.authelia.url}/api/oidc/token"'';
|
||||
description = ''
|
||||
The OIDC token endpoint the controller mints its own access
|
||||
|
|
@ -773,12 +773,13 @@ in
|
|||
{
|
||||
assertion = cfg.queue.tokenEndpoint != "";
|
||||
message = ''
|
||||
services.hyperhive.swarm.controller.queue.tokenEndpoint is unset,
|
||||
which means services.hyperhive.swarm.authelia.url is null.
|
||||
services.hyperhive.swarm.controller.queue.tokenEndpoint is unset.
|
||||
|
||||
The controller mints its own access token before it may connect
|
||||
to the queue, so it needs to know where the swarm's identity
|
||||
provider lives — set that URL, or set this endpoint directly.
|
||||
It defaults from services.hyperhive.swarm.authelia.url, so this
|
||||
only happens when it has been cleared by hand. The controller
|
||||
mints its own access token before it may connect to the queue,
|
||||
so it needs to know where the swarm's identity provider lives —
|
||||
set this endpoint directly.
|
||||
'';
|
||||
}
|
||||
{
|
||||
|
|
|
|||
|
|
@ -109,18 +109,6 @@ let
|
|||
# operator sees, not a coercion error from here.
|
||||
domainBase = if swarmDomain == null then "invalid" else swarmDomain;
|
||||
|
||||
# Is SSO configured for this SWARM. Swarm-wide by construction — `swarm.*` is
|
||||
# identical on every host — and the option's own description is what makes
|
||||
# this the right question to ask: a null URL means "no SSO configured".
|
||||
#
|
||||
# 🩸 A subject of an assertion below, NOT a gate. Dropping the OIDC block when
|
||||
# this is false looks conservative and is the outage: `disable_login_form` is
|
||||
# unconditional a few hundred lines down, so a Grafana with no OIDC settings
|
||||
# is a Grafana with no login of any kind, arrived at silently. SSO is a
|
||||
# requirement of running this service, so an unconfigured swarm fails to
|
||||
# build and says which option to set.
|
||||
ssoConfigured = hyperhiveCfg.swarm.authelia.url != null;
|
||||
|
||||
# A reader of the store is defined by holding a certificate the store
|
||||
# accepts, never by standing next to it — the rule
|
||||
# ./glue-matrix-bao-token.nix states in full.
|
||||
|
|
@ -448,34 +436,11 @@ in
|
|||
# host that runs authelia, and this whole block is gated on the host that
|
||||
# runs Grafana. ./glue-grafana-oidc-client.nix is where it moved to.
|
||||
|
||||
# Both arms are what used to be a silent gate, and both fire only where
|
||||
# Grafana runs — this whole block is under `deploy.grafana.enable`. The
|
||||
# binding itself is asserted rather than a copy of its formula, the way
|
||||
# ./swarm-nats.nix's own `autheliaUrl` arm does it: two spellings of one
|
||||
# boolean is two places for a future edit to land in only one.
|
||||
# This arm fires only where Grafana runs — this whole block is under
|
||||
# `deploy.grafana.enable`. SSO itself needs no assertion any more:
|
||||
# `swarm.authelia.url` is non-nullable, so every swarm has one and the
|
||||
# OIDC block below always names a real endpoint.
|
||||
assertions = [
|
||||
{
|
||||
# SSO is not optional for this service, and the reason is a hundred
|
||||
# lines below in `auth.disable_login_form = true`: Grafana ships an
|
||||
# `admin`/`admin` account on a public vhost, so the password box is
|
||||
# off whatever the topology. Emitting no OIDC block when the swarm
|
||||
# names no IdP therefore produces a container with no way in at all —
|
||||
# a state no log names, since nothing failed. Failing the build and
|
||||
# naming the option is the only report that reaches anyone.
|
||||
assertion = ssoConfigured;
|
||||
message = ''
|
||||
services.hyperhive.deploy.grafana.enable requires
|
||||
services.hyperhive.swarm.authelia.url — Grafana's only login is SSO,
|
||||
because its local login form is disabled unconditionally (it ships
|
||||
an admin/admin account and its vhost is on the public gateway).
|
||||
|
||||
It defaults to this host's own instance only when this host runs
|
||||
authelia. A hive that federates with a swarm sets it explicitly to
|
||||
wherever that provider lives. Grafana exchanges its authorization
|
||||
code at `''${url}/api/oidc/token` from inside its container, so a
|
||||
null URL leaves no endpoint to name.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# The other half of one login: the OIDC block names a `$__file{}` that
|
||||
# `swarm-bao-grafana-oidc.service` below writes, and that unit reads
|
||||
|
|
|
|||
|
|
@ -511,18 +511,6 @@ in
|
|||
to have this host mint all four.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = autheliaUrl != null;
|
||||
message = ''
|
||||
services.hyperhive.deploy.nats.enable requires
|
||||
services.hyperhive.swarm.authelia.url — the queue authenticates
|
||||
clients by validating tokens that authelia issued.
|
||||
|
||||
It defaults to this host's own instance only when this host
|
||||
runs authelia. A hive that federates with a swarm sets it
|
||||
explicitly to wherever that provider lives.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
# One declaration, two readers. The queue knows which client id it
|
||||
|
|
|
|||
|
|
@ -1027,22 +1027,6 @@ in
|
|||
should not be described as both.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# A hive proves who it is with a token this provider mints, so
|
||||
# there is no version of this collector that runs without one.
|
||||
# Stated as an assertion rather than a fallback because a guessed
|
||||
# issuer URL evaluates cleanly and refuses every hive at runtime.
|
||||
assertion = hyperhiveCfg.swarm.authelia.url != null;
|
||||
message = ''
|
||||
services.hyperhive.deploy.swarm-otel.enable is true but
|
||||
services.hyperhive.swarm.authelia.url is null: every hive
|
||||
authenticates to this collector as itself, and the token comes
|
||||
from the swarm's identity provider.
|
||||
|
||||
Point authelia.url at the swarm's provider, or enable
|
||||
services.hyperhive.swarm.authelia on the host that runs it.
|
||||
'';
|
||||
}
|
||||
{
|
||||
# A port collision between two listeners on one host is a runtime
|
||||
# coin toss with nothing in any log — the failure this whole
|
||||
|
|
|
|||
|
|
@ -500,8 +500,8 @@ in
|
|||
options.services.hyperhive.swarm.statusPublish = {
|
||||
tokenEndpoint = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = if swarmCfg.authelia.url != null then "${swarmCfg.authelia.url}/api/oidc/token" else null;
|
||||
defaultText = lib.literalExpression ''"''${swarm.authelia.url}/api/oidc/token" when that URL is set, else null'';
|
||||
default = "${swarmCfg.authelia.url}/api/oidc/token";
|
||||
defaultText = lib.literalExpression ''"''${swarm.authelia.url}/api/oidc/token"'';
|
||||
example = "https://auth.example.com/api/oidc/token";
|
||||
description = ''
|
||||
The swarm IdP's OAuth2 token endpoint. This hive mints a
|
||||
|
|
|
|||
|
|
@ -70,22 +70,6 @@ let
|
|||
swarm.authelia.url = "https://auth.example.invalid";
|
||||
};
|
||||
|
||||
# The mirror image: the identity is placed, and the swarm names no IdP. The
|
||||
# other half of "SSO must always be configured", and isolated the same way —
|
||||
# exactly one thing wrong, so the arm reads one refusal.
|
||||
#
|
||||
# The null is now written out: `swarm.authelia.url` defaults to the swarm's
|
||||
# IdP name on every hive, so "this swarm has no IdP" is a thing an operator
|
||||
# states rather than a thing not running the container produces.
|
||||
grafanaNoSso = hive {
|
||||
deploy.grafana.enable = true;
|
||||
deploy.grafana.plugins = [ ];
|
||||
deploy.grafana.package = pkgs.emptyDirectory;
|
||||
deploy.bao.clientCertFile = "/etc/pki/bao-client.pem";
|
||||
deploy.bao.clientKeyFile = "/etc/pki/bao-client-key.pem";
|
||||
swarm.authelia.url = null;
|
||||
};
|
||||
|
||||
# Did ./host-modules/swarm-grafana.nix refuse this host, and for which of its
|
||||
# two reasons. An assertion is a config VALUE until something forces it —
|
||||
# `.config` never throws — so a fixture in a state the module refuses is
|
||||
|
|
@ -169,19 +153,7 @@ let
|
|||
&& !(grafanaRefusedFor grafanaNoIdentity "swarm.authelia.url");
|
||||
}
|
||||
{
|
||||
# "SSO must always be configured", as an eval-time refusal rather than a
|
||||
# gate. A null URL used to drop the OIDC block silently, and
|
||||
# `disable_login_form` is unconditional a hundred lines below it, so that
|
||||
# combination produced a Grafana with no SSO and no password box — an
|
||||
# outage whose cause is a boolean that evaluated to false at build time
|
||||
# and left no trace. Same isolation as the arm above, mirrored.
|
||||
name = "a grafana host in a swarm with no IdP is refused, naming swarm.authelia.url";
|
||||
ok =
|
||||
grafanaRefusedFor grafanaNoSso "services.hyperhive.swarm.authelia.url"
|
||||
&& !(grafanaRefusedFor grafanaNoSso "deploy.bao.clientCertFile");
|
||||
}
|
||||
{
|
||||
# Without this the two arms above prove nothing: a refusal that fires on
|
||||
# Without this the arm above proves nothing: a refusal that fires on
|
||||
# every host is not a check, and both of these are hosts a swarm is
|
||||
# expected to have. Read through the same helper, so a message that
|
||||
# stopped naming its option would fail the arms above rather than pass
|
||||
|
|
|
|||
Loading…
Reference in a new issue