Watch
0
0
Fork
You've already forked hyperhive
0

hive-tls owns the per-host list of locally fronted service domains

This commit is contained in:
atlas 2026-10-17 18:48:52 +02:00 • committed by mara
commit 22b9c570ba
9 changed files with 605 additions and 597 deletions

View file

@ -146,7 +146,7 @@ same list on every host, populates the role's
`allowed_domains`, so asking for a name nobody configured is a refusal
from the store naming that name — not a certificate quietly issued for
it. A host's own SANs are narrower still: it asks only for the names it
fronts a vhost for (`swarm.localServiceDomains`), so the key on one
fronts a vhost for (`deploy.hive-controller.tls.localServiceDomains`), so the key on one
gateway can't serve a swarm service that runs behind another.
**The root's public certificate is a file, on every self-signed gateway host:**

View file

@ -20,11 +20,11 @@ let
baoServicesPkiMount = baoDeploy.servicesPkiMountPath;
baoServicesPkiRole = baoDeploy.servicesPkiRoleName;
# Derived once in ./swarm.nix: the swarm service names THIS host fronts
# a vhost for. Every one of them is inside the swarm-wide set that
# ./swarm-bao.nix narrows `pki/roles/swarm-services` to, so the leaf
# stays issuable while carrying no name this gateway does not serve.
localServiceDomains = hyperhiveCfg.swarm.localServiceDomains;
# The swarm service names THIS host fronts a vhost for. Every one of them
# is inside the swarm-wide set that ./swarm-bao.nix narrows
# `pki/roles/swarm-services` to, so the leaf stays issuable while
# carrying no name this gateway does not serve.
localServiceDomains = cfg.localServiceDomains;
# The one name the request's `common_name` carries; the rest ride as
# SANs. Not `builtins.head`: a host fronting no swarm service renders
@ -346,9 +346,41 @@ in
Both or neither — a certificate with no key authenticates nothing.
'';
};
localServiceDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
readOnly = true;
internal = true;
description = ''
Read-only: the names in
{option}`services.hyperhive.swarm.serviceDomains` **this host
actually fronts**, which is what its services leaf may carry. The
swarm-wide set stays what the sub-CA is name-constrained to and what
the store's issuing role permits; narrowing the leaf is what stops a
private key on one gateway presenting a valid certificate for
services that host does not serve and never has.
Read off the rendered `services.nginx.virtualHosts` rather than off
the per-service deploy flags: those flags are what the vhosts are
already guarded on, so a second reading of them is a copy that
drifts from the thing it claims to describe. Filtering
`swarm.serviceDomains` rather than assembling a list beside it is
that option's own constraint, and it applies here for the same
reason.
'';
};
};
config = lib.mkIf active {
config = lib.mkMerge [
{
# Outside `active`: the option has no default, so reading it on a host
# whose gateway is not self-signed would otherwise throw.
services.hyperhive.deploy.hive-controller.tls.localServiceDomains = lib.filter (
d: config.services.nginx.virtualHosts ? ${d}
) hyperhiveCfg.swarm.serviceDomains;
}
(lib.mkIf active {
# The state dir and the trust bundle. Never waits on the store: the
# store's container is ordered after this unit, which writes the
# bundle it bind-mounts, so the leaf is `hive-tls-resign`'s.
@ -929,5 +961,6 @@ in
Persistent = true;
};
};
};
})
];
}

View file

@ -2470,7 +2470,7 @@ in
every gateway host, whether or not a hive runs there.
Required on every self-signed gateway host that fronts a swarm-service
name ({option}`services.hyperhive.swarm.localServiceDomains` not
name ({option}`services.hyperhive.deploy.hive-controller.tls.localServiceDomains` not
empty). Unset there, the host evaluates and `swarm-services-cert`
fails, naming both options. A host fronting none only fetches the
store's root, presenting

View file

@ -90,7 +90,7 @@ let
# Every name is listed whether or not this host runs the service: the
# store's issuing role is built from this list on the store's host and
# has to admit a leaf for any gateway in the swarm. What one gateway's
# leaf carries is `localServiceDomains`.
# leaf carries is `deploy.hive-controller.tls.localServiceDomains`.
#
# Membership is also what `gateway.lib.tlsFor` consults to pick the
# services leaf over the hive one, so a gateway-published swarm name
@ -278,27 +278,6 @@ in
'';
};
options.services.hyperhive.swarm.localServiceDomains = lib.mkOption {
type = lib.types.listOf lib.types.str;
readOnly = true;
internal = true;
description = ''
Read-only: the names in `serviceDomains` **this host actually
fronts**, which is what its services leaf may carry. The swarm-wide
set stays what the sub-CA is name-constrained to and what the
store's issuing role permits; narrowing the leaf is what stops a
private key on one gateway presenting a valid certificate for
services that host does not serve and never has.
Read off the rendered `services.nginx.virtualHosts` rather than off
the per-service deploy flags: those flags are what the vhosts are
already guarded on, so a second reading of them is a copy that
drifts from the thing it claims to describe. Filtering
`serviceDomains` rather than assembling a list beside it is that
option's own constraint, and it applies here for the same reason.
'';
};
config = {
# Total on a null `swarm.domain` for the reason `hives.<name>.domain`'s
# default gives.
@ -317,10 +296,6 @@ in
lib.unique (lib.filter (d: d != null && d != "") serviceDomains')
);
services.hyperhive.swarm.localServiceDomains = lib.filter (
d: config.services.nginx.virtualHosts ? ${d}
) swarmCfg.serviceDomains;
assertions = [
{
# An EMPTY `hives` fires this too, deliberately: since

View file

@ -374,7 +374,7 @@ let
m:
builtins.elem "hive-gateway-self-signed-cert.service" m.systemd.services.swarm-services-cert.before;
in
baoPkcs11.services.hyperhive.swarm.localServiceDomains == [ "bao-ui.t.local" ]
baoPkcs11.services.hyperhive.deploy.hive-controller.tls.localServiceDomains == [ "bao-ui.t.local" ]
&& baoPkcs11.systemd.services ? hive-gateway-self-signed-cert
&& !(waits baoPkcs11)
&& !(orders baoPkcs11)

View file

@ -361,7 +361,7 @@ let
# vhosts are the hive leaf's, which this host still signs.
name = "a host fronting none of the swarm's service names requests no services leaf";
ok =
bare.services.hyperhive.swarm.localServiceDomains == [ ]
bare.services.hyperhive.deploy.hive-controller.tls.localServiceDomains == [ ]
&& bare.services.hyperhive.swarm.serviceDomains != [ ]
&& lib.hasInfix "want_svc=0" bare.systemd.services.swarm-services-cert.script;
}

View file

@ -222,7 +222,7 @@ let
# that asks the store for no leaf.
name = "a hive fronting no swarm name puts the store's pki root in its trust bundle";
ok =
noService.services.hyperhive.swarm.localServiceDomains == [ ]
noService.services.hyperhive.deploy.hive-controller.tls.localServiceDomains == [ ]
&& lib.hasInfix ''"$BAO_ADDR/v1/pki/ca/pem"'' noSvcFetch
&& lib.hasInfix ''mv -f "$svcroot.new" "$svcroot"'' noSvcFetch
&& lib.hasInfix ''svcroot="$d/swarm-services-root.pem"'' noService.systemd.services.hive-tls-ca.script
@ -255,7 +255,7 @@ let
# control is the same hive with an identity, whose branch fetches.
name = "a hive fronting no swarm name with no store identity fails the unit, naming the hive identity's options";
ok =
noIdentity.services.hyperhive.swarm.localServiceDomains == [ ]
noIdentity.services.hyperhive.deploy.hive-controller.tls.localServiceDomains == [ ]
&& noIdentity.services.hyperhive.deploy.hive-controller.tls.baoHiveIdentityCertFile == null
&& !(lib.hasInfix "/v1/pki/ca/pem" noIdFetch)
&& lib.hasInfix "baoHiveIdentityCertFile (unset)" (noSvcBranch noIdFetch)

View file

@ -420,7 +420,7 @@ let
&&
queueNoStore.services.nginx.virtualHosts.${natsName}.sslCertificate
== "/var/lib/swarm-nats-gateway-tls/cert.pem"
&& h.swarm.localServiceDomains == [ ]
&& h.deploy.hive-controller.tls.localServiceDomains == [ ]
&& !(lib.elem natsName h.swarm.serviceDomains)
&& !(lib.elem "hive-gateway-self-signed-cert.service" (s.swarm-services-cert.requiredBy or [ ]))
&& !(lib.hasInfix "serves swarm names only with it" s.hive-gateway-self-signed-cert.script)

View file

@ -500,7 +500,7 @@ let
# The store's UI alone: its nginx carries the passthrough to the
# store, so it starts on the fallback with the store sealed.
&&
store.services.hyperhive.swarm.localServiceDomains
store.services.hyperhive.deploy.hive-controller.tls.localServiceDomains
== [ store.services.hyperhive.swarm.bao.ui.domain ]
&& !(waitsForLeaf store)
# The homeserver without a hive: no hive-domain vhost at all, and the