swarm-bao: make the store's name resolve where the store runs

Nothing resolved `bao.<swarm.domain>`. It is absent from
`swarm.serviceDomains` and, unlike every other swarm service, contributed
no `gateway.localNames` entry — so the name a reader dials answered only
if an operator had published an external record, which nothing asks them
to do.

Cross-hive traffic goes via the domain either way; only what it resolves
to varies, and for a multi-host swarm that is the operator's upstream DNS.
This covers the deployment with no upstream record to configure.

DNS only, and that asymmetry is deliberate: bao stays out of
`serviceDomains` and gets no vhost. Its leaf comes from its own CA, so a
services-CA entry would mint one nothing uses, and nginx terminating TLS
would strip the client certificate the store authenticates every hive
with. `swarm-nats.nix` documents the same exclusion for itself.

module-eval gains the presence and absence arms: claiming a name this
host does not serve would point every local reader at the wrong machine.

Refs #3862
This commit is contained in:
atlas 2026-08-31 21:15:21 +02:00 committed by mara
commit f3ce94b4f6
2 changed files with 26 additions and 0 deletions

View file

@ -418,6 +418,18 @@ in
'';
}
];
# The name every reader dials, made resolvable where the store runs.
# Cross-hive traffic always goes via the domain; only what it resolves
# to varies, and a multi-host swarm is the operator's upstream DNS. This
# covers the case that has no upstream record to configure.
#
# ⚠️ DNS only. Bao is deliberately absent from `swarm.serviceDomains`
# and gets no vhost: nginx terminating TLS would strip the client
# certificate, which is how the store authenticates every hive — see
# this file's header. `localNames` is the one half of the sibling
# pattern that applies.
services.hyperhive.gateway.localNames = [ cfg.domain ];
})
(lib.mkIf (hyperhiveCfg.enable && deployCfg.bao.enable && haveServerTls) {

View file

@ -103,6 +103,8 @@ let
# is deployed" from "this host can authenticate to the store".
matrixNoBaoIdentity = hive { deploy.matrix.enable = true; };
baoNames = machine: machine.services.hyperhive.gateway.localNames;
# A priority collision is a property of the *option*, not
# of the merged value's interior — nix throws the moment the value is
# demanded at all, so `seq`-ing each `serviceConfig` value to WHNF is
@ -250,6 +252,18 @@ let
in
s ? swarm-bao-matrix-token && s.swarm-bao-matrix-token.requires == [ "swarm-bao-pki.service" ];
}
{
# The name a reader dials has to resolve where the store runs; a
# multi-host swarm resolves it upstream instead.
name = "the store's host answers for the store's name";
ok = builtins.elem "bao.t.local" (baoNames baoPkcs11);
}
{
# Absence arm, and the one that matters: claiming a name this host does
# not serve points every local reader at the wrong machine.
name = "a hive that does not run the store claims no name for it";
ok = !(builtins.elem "bao.t.local" (baoNames bare));
}
];
bad = builtins.filter (c: !c.ok) cases;