fix(nix): drop the swarm-service-domain assertion, it asserted intent

Measured against a real config, not reasoned about: pinning
`swarm.forge.domain = "forge.<hive domain>"` while `swarm.domain` is a
different apex -- the documented way an existing deployment keeps its
names while joining a swarm -- FAILED eval under this assertion.

And the reason it gave was false. It said those names are certified by
the services sub-CA, "which is constrained to the swarm's own tree, so a
name outside it has no issuer here". The sub-CA is constrained to the
**configured names** (swarm-ca.nix builds its nameConstraints from the
derived list), and the swarm root carries no name constraints at all --
only basicConstraints and keyUsage. So any configured name is issuable,
and a legacy name under the hive domain is additionally signable by the
hive CA.

The assertion therefore encoded the shape I had in mind rather than a
constraint the code implements, and it rejected a configuration that
works. Removed rather than weakened: a narrower version would still
carry a claim nothing backs.

`serviceDomains` stays -- it feeds the sub-CA's name constraints, the
leaf's SANs and the per-vhost cert choice, and those three agreeing is
the thing that mattered. The (option, value) pairs collapse back to a
plain list, since the provenance existed only for the assertion message.
This commit is contained in:
atlas 2026-08-05 22:22:09 +02:00 committed by mara
commit 30551464c1

View file

@ -24,23 +24,21 @@ let
cfg = config.services.hyperhive;
swarmCfg = cfg.swarm;
# The service hostnames with the option that set each one, so an
# assertion can name the thing to edit rather than the value to hunt
# for. `serviceDomains` below is the same set flattened; this keeps
# the provenance that flattening drops.
namedServiceDomains = [
{
option = "services.hyperhive.swarm.forge.domain";
value = swarmCfg.forge.domain;
}
{
option = "services.hyperhive.swarm.matrix.gatewayHost";
value = swarmCfg.matrix.gatewayHost;
}
{
option = "services.hyperhive.swarm.authelia.domain";
value = swarmCfg.authelia.domain;
}
# Public hostnames of the swarm's own services, in declaration order.
# `serviceDomains` below is this set sorted + deduplicated.
#
# ⚠️ These are NOT required to be under `swarm.domain`. An earlier
# revision asserted that, reasoning that the services sub-CA is
# constrained to the swarm's tree — but the sub-CA is constrained to
# the **configured names** (./swarm-ca.nix) and the swarm root carries
# no name constraints at all, so any configured name is issuable. The
# assertion encoded an intended shape, not a property of the code, and
# it rejected the supported migration path: a hive pinning its old
# `forge.<hive domain>` while joining a swarm at a different apex.
serviceDomains' = [
swarmCfg.forge.domain
swarmCfg.matrix.gatewayHost
swarmCfg.authelia.domain
];
in
{
@ -194,48 +192,10 @@ in
services.hyperhive.swarm.peerHives = lib.filterAttrs (name: _: name != cfg.hiveName) swarmCfg.hives;
services.hyperhive.swarm.serviceDomains = lib.sort (a: b: a < b) (
lib.unique (lib.filter (d: d != null && d != "") (lib.map (s: s.value) namedServiceDomains))
lib.unique (lib.filter (d: d != null && d != "") serviceDomains')
);
assertions = [
{
# Every swarm-service name must live under `swarm.domain`.
#
# This is not style. Those names are certified by the
# swarm-services sub-CA (./swarm-ca.nix), which is
# name-constrained; a name outside the swarm's tree has no
# issuer in this deployment at all. Asserting it here turns
# "your certs mysteriously don't work" into an eval failure that
# names the option — and it applies to every deployment, not
# just the all-local one, because the operator bringing their
# own certs needs to know which names they are bringing them
# for.
#
# Guarded on a null `swarm.domain` so the required-domain
# assertion in ./hive-network.nix stays the one that fires for
# that case; two assertions naming the same missing value is
# noise.
assertion =
swarmCfg.domain == null
|| lib.all (s: s.value == null || lib.hasSuffix ".${swarmCfg.domain}" s.value) namedServiceDomains;
message =
let
bad = lib.filter (
s: s.value != null && !lib.hasSuffix ".${swarmCfg.domain}" s.value
) namedServiceDomains;
in
''
Swarm service hostnames must be sub-domains of
services.hyperhive.swarm.domain ("${toString swarmCfg.domain}"):
${lib.concatMapStringsSep "\n" (s: " ${s.option} = \"${s.value}\";") bad}
These names are issued by the swarm-services sub-CA, which is
constrained to the swarm's own tree a name outside it has no
issuer here, and no certificate this deployment can produce
will match it.
'';
}
{
# Guarded on `hiveName != null` so the required-hiveName
# assertion in ./hyperhive.nix is what fires for that case —