feat(nix): warn when a swarm service name has no certificate path here

The swarm's service names default to siblings of the hive domain
(forge.<swarm.domain>, not forge.<hive domain>), and the hive CA's leaf
is a single-label wildcard over its own domain, so it cannot cover them.
The swarm-services leaf can — but only on a host that holds the swarm
root key, i.e. swarm.ca.autoConfigure. Everywhere else the gateway
quietly serves the hive leaf on those names and every client sees a
mismatch, on a config that evaluates and deploys cleanly.

A warning rather than an assertion, per the operator's call. This module
knows what it can issue; it cannot see an operator-installed services
sub-CA or an external ACME setup, so "no certificate path" is a thing it
observes, not a thing it can conclude. A rebuild must not be blocked by
a verdict this host isn't in a position to reach — the message says what
was observed and names both ways out.
This commit is contained in:
atlas 2026-08-11 18:20:08 +02:00 committed by mara
commit 155df39ee4

View file

@ -15,6 +15,21 @@ let
# constrained to cannot disagree.
swarmServiceDomains = hyperhiveCfg.swarm.serviceDomains;
# True for the names `signHiveLeaf` below actually covers: the hive
# domain itself, or ONE label under it. `*.<domain>` is a single-label
# wildcard — `a.b.<domain>` does not match it — so the depth check is
# the whole point rather than a nicety.
coveredByHiveLeaf =
n:
n == domain
|| (lib.hasSuffix ".${domain}" n && !lib.hasInfix "." (lib.removeSuffix ".${domain}" n));
# Service names this host can serve a *matching* certificate for, and
# the ones it can't. A name is issuable here when the hive leaf covers
# it, or when this host issues the swarm-services leaf — which needs
# the swarm root's private key, i.e. `swarm.ca.autoConfigure`.
uncoveredServiceDomains = lib.filter (n: !coveredByHiveLeaf n) swarmServiceDomains;
# The host-managed hive CA is the trust anchor for self-signed mode.
# It is only stood up when the gateway actually serves a self-signed
# cert: the gateway must be in self-signed mode. `domain` is required
@ -244,6 +259,41 @@ in
};
config = lib.mkIf active {
# A swarm service name that is a sibling of the hive domain rather
# than a child needs the swarm-services leaf, and this host only
# signs that one when it holds the swarm root key. Without it nginx
# falls back to the hive leaf on those names and every client sees a
# name mismatch — on a config that evaluates and deploys cleanly.
#
# ⚠️ A WARNING, NOT AN ASSERTION, and the distinction is the point:
# this module can see what *it* can issue; it cannot see an
# operator-installed services sub-CA, an external ACME setup, or a
# cert delivered by any other means. A rebuild must not be blocked
# by a conclusion this host is not in a position to reach. Report
# the observation and let the operator judge it.
#
# (`certDir` / ACME modes don't reach here at all: `active` is
# self-signed-only, and there the operator's cert decides.)
warnings = lib.optional (uncoveredServiceDomains != [ ] && !swarmCaCfg.autoConfigure) ''
services.hyperhive: these swarm service names are outside this
hive's domain (${domain}), so the hive CA's leaf does not cover
them:
${lib.concatMapStringsSep "\n" (n: " ${n}") uncoveredServiceDomains}
services.hyperhive.swarm.ca.autoConfigure is false, so this host
does not sign the swarm-services leaf either, and the gateway will
serve the hive leaf on those names a certificate-name mismatch
for browsers and for agents' git-over-https.
If you have already arranged certificates for them an
operator-installed services sub-CA in ${swarmCaCfg.stateDir}, or
services.hyperhive.gateway.tls.{certDir,acme} this is expected
and you can ignore it. Otherwise pin the names back under
${domain} (services.hyperhive.swarm.{forge.domain,
matrix.gatewayHost, authelia.domain}) or install the sub-CA.
'';
# Generate (and rotate) the hive CA + gateway leaf before the gateway
# container starts. Idempotent: the CA is created once and reused; the
# leaf is re-signed on expiry under the same CA so the anchor is stable.