swarm: guard hive names where the roster is declared, and reserve the cert subjects
The two hive-name guards lived in swarm-otel.nix, inside its `config = lib.mkIf (… && deployCfg.swarm-otel.enable)`. A swarm running the secret store and the controller but no collector therefore had no hive-name check at all, while the names were still composed into OIDC client ids, bao policies and cert-auth roles exactly the same way. They move to swarm.nix, which declares `swarm.hives` and is unconditional. swarm-otel keeps the assertion that its own entry is still in the shared list — that one is about this module's stake in a file it no longer controls. The equality guard also takes the store's cert-auth subjects now. Cert auth trusts the CA, so `allowed_common_names` is the whole of what narrows a role to one identity, and the same CA signs every hive's leaf with the hive's name as its CN. A hive named after a role's subject presents a certificate that role accepts, which for the controller is write access to every hive's credentials and policies. A list rather than the one string, because the next role added beside it widens what a hive name must not collide with, and because the subject is an option an operator sets — a literal deny entry covers the default and nothing else. Four module-eval cases, two of them controls. The fixture overrides the subject to `ctl` on purpose: the default contains `swarm`, which the substring guard catches whatever the new arm does, so a fixture using it could not tell the two apart. The controls are that a legal roster trips neither guard, and that all three fixtures really do have the collector disabled — without the second, every case would pass while testing the arrangement they exist to rule out.
This commit is contained in:
parent
613ca541e1
commit
bb62bf1aa9
3 changed files with 129 additions and 31 deletions
|
|
@ -25,6 +25,26 @@ let
|
|||
swarmCfg = cfg.swarm;
|
||||
deployCfg = cfg.deploy;
|
||||
|
||||
hiveNames = lib.attrNames swarmCfg.hives;
|
||||
|
||||
nameGuards = import ./lib/name-guards.nix { inherit lib; };
|
||||
|
||||
# Names no hive may BE, and words no hive name may CONTAIN. Both files
|
||||
# explain their own admission rules; the second says why they are not one
|
||||
# list. They are applied HERE rather than in the module that happens to
|
||||
# consume them, because a hive name composes identifiers whatever else is
|
||||
# enabled — ./swarm-otel.nix used to hold these guards and gated them on its
|
||||
# own `enable`, so a swarm without the collector had no check at all.
|
||||
reservedNames = import ../reserved-names.nix;
|
||||
reservedFragments = import ../reserved-hive-fragments.nix;
|
||||
|
||||
# Subjects the store's cert-auth roles accept. A LIST, not one string: cert
|
||||
# auth trusts the CA and `allowed_common_names` is the whole narrowing, so
|
||||
# every role added beside these widens what a hive name must not collide
|
||||
# with. A hive's own leaf carries its name as the CN, so a hive named after
|
||||
# one of these presents a certificate that role accepts.
|
||||
certAuthCns = [ deployCfg.bao.controllerCommonName ];
|
||||
|
||||
# Public hostnames of the swarm's own services, in declaration order.
|
||||
# `serviceDomains` below is this set sorted + deduplicated.
|
||||
#
|
||||
|
|
@ -387,6 +407,42 @@ in
|
|||
swarm, or set all three to null to turn publishing off.
|
||||
'';
|
||||
}
|
||||
(nameGuards.mustNotEqual {
|
||||
option = "services.hyperhive.swarm.hives";
|
||||
names = hiveNames;
|
||||
reserved = reservedNames ++ certAuthCns;
|
||||
why = ''
|
||||
A hive's name is what other components address it by, and two
|
||||
of those uses resolve the clash silently rather than erroring.
|
||||
|
||||
The collector names components `<kind>/<owner>` with the hive
|
||||
name as owner, and `//` merges them: the swarm tier's parts
|
||||
win, that hive's pipeline and `hive=` stamp disappear, and it
|
||||
keeps pushing into a route that goes nowhere.
|
||||
|
||||
The secret store's cert-auth roles match on a certificate's
|
||||
common name, and a hive's own leaf carries its name. A hive
|
||||
named after a role's subject presents a certificate that role
|
||||
accepts — so it receives that principal's grants, which for
|
||||
the controller means write access to every hive's credentials
|
||||
and policies.
|
||||
|
||||
Rename the hive.
|
||||
'';
|
||||
})
|
||||
(nameGuards.mustNotContain {
|
||||
option = "services.hyperhive.swarm.hives";
|
||||
names = hiveNames;
|
||||
fragments = reservedFragments;
|
||||
why = ''
|
||||
Hive-scoped identifiers are composed from a hive name —
|
||||
`hive-<name>`, `hive-<name>-agent` — so a name containing one of
|
||||
these produces an identifier that is also somebody else's. The
|
||||
failure is a wrong grant rather than an error: the client
|
||||
authenticates and receives another principal's permissions, and a
|
||||
NATS denial arrives as a timeout. Rename the hive.
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue