swarm-tls: narrow each gateway's services leaf to the names it fronts
Every gateway asked the store's `pki/issue/swarm-services` for the whole swarm's service set, so a private key on any gateway host could serve a valid certificate for services that host does not front and never has. `swarm.localServiceDomains` derives the per-host subset by filtering `swarm.serviceDomains` against the vhosts this host actually renders — the deploy flags those vhosts are already guarded on, read once rather than copied into a second filter. The leaf request and the coverage guard that decides whether to re-issue both read it, so they cannot disagree about which names the leaf owes. The sub-CA's name constraint and the role's `allowed_domains` stay the swarm-wide set: every host's subset is inside it, and narrowing the constraint per host would turn one signing into N.
This commit is contained in:
parent
0649673ebf
commit
ed2ec52fe5
5 changed files with 89 additions and 20 deletions
|
|
@ -52,6 +52,10 @@ let
|
|||
deploy.forgejo.ci.enable = true;
|
||||
};
|
||||
|
||||
# A hive whose one gateway-published swarm service sits on another host:
|
||||
# every swarm name still configured, not one of them served here.
|
||||
forgeElsewhere = hive { deploy.forgejo.behindGateway = false; };
|
||||
|
||||
# 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
|
||||
|
|
@ -199,6 +203,32 @@ let
|
|||
in
|
||||
(l.tlsFor "t.local").sslCertificate != (l.tlsFor "_").sslCertificate;
|
||||
}
|
||||
{
|
||||
# A leaf is a key that can SERVE every name in it, so the names it
|
||||
# asks for are that key's blast radius. `bare` fronts forge and
|
||||
# nothing else; the last conjunct is the control, since `auth.t.local`
|
||||
# is in the swarm's set and an unnarrowed request would carry it here
|
||||
# too.
|
||||
name = "a gateway's services leaf asks for only the swarm names that host fronts";
|
||||
ok =
|
||||
let
|
||||
s = bare.services.hyperhive.swarm.serviceDomains;
|
||||
u = bare.systemd.services.swarm-services-cert.script;
|
||||
in
|
||||
lib.hasInfix "alt_names=forge.t.local" u
|
||||
&& !(lib.hasInfix "auth.t.local" u)
|
||||
&& lib.elem "auth.t.local" s;
|
||||
}
|
||||
{
|
||||
# The narrowing's floor: no names left means no request, rather than a
|
||||
# request for an empty SAN set that the store would refuse. Its own
|
||||
# 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 =
|
||||
forgeElsewhere.services.hyperhive.swarm.localServiceDomains == [ ]
|
||||
&& forgeElsewhere.services.hyperhive.swarm.serviceDomains != [ ]
|
||||
&& lib.hasInfix "want_svc=0" forgeElsewhere.systemd.services.swarm-services-cert.script;
|
||||
}
|
||||
{
|
||||
# nixos asserts when a vhost declares both, so this is also a
|
||||
# statement that the `removeAttrs` upstream of it still happens.
|
||||
|
|
|
|||
Loading…
Reference in a new issue