# `checks.module-eval-name-guards` — see ./lib.nix for the shared # rationale (why this suite exists, naming convention, "evaluates # not executes"). { pkgs, lib, self, nixosSystem, }: let inherit (import ./lib.nix { inherit pkgs lib self nixosSystem ; }) hive runGroup ; # The hive-name guards, with the collector explicitly OFF. That is the whole # property: the guards live where `swarm.hives` is declared, so they run in a # deployment that has a secret store and no collector — which used to skip # them entirely, because they were assertions inside swarm-otel's own `mkIf`. # # ⚠️ `controllerCommonName` is overridden to a name containing NO reserved # fragment. Its default (`swarm-controller`) contains `swarm` and is caught # by the substring guard whatever the cert-auth arm does — so a fixture using # the default could not tell the two apart, and the arm under test would pass # on the neighbour's work. hiveNamedAfterCertSubject = hive { deploy.swarm-otel.enable = false; deploy.bao.controllerCommonName = "ctl"; swarm.hives.ctl.domain = "ctl.t.local"; }; # The control for both arms below: same shape, a roster nothing objects to. hiveNamesAllLegal = hive { deploy.swarm-otel.enable = false; deploy.bao.controllerCommonName = "ctl"; }; # The reserved subjects are a LIST, and a list with one consulted element and # one dead one looks identical from the first element's case. This fixture # collides with the SECOND, leaving the controller's at its default. hiveNamedAfterPublisherSubject = hive { deploy.swarm-otel.enable = false; deploy.bao.secretPublisherCommonName = "pubctl"; swarm.hives.pubctl.domain = "p.t.local"; }; # The THIRD element of the same list, colliding on its own so neither of the # two above can carry it. matrix-ctl's grant is one path rather than a whole # prefix, which is exactly why a dead entry here would be easy to miss: a # hive that inherited it would not obviously break anything, it would # silently gain the ability to overwrite the swarm's matrix credential. hiveNamedAfterMatrixCtlSubject = hive { deploy.swarm-otel.enable = false; deploy.bao.matrixCtlCommonName = "mintctl"; swarm.hives.mintctl.domain = "m.t.local"; }; # The two OIDC-secret readers' subjects, fixed strings like the three above. hiveNamedAfterGrafanaOidcSubject = hive { deploy.swarm-otel.enable = false; deploy.bao.grafanaOidcCommonName = "gfctl"; swarm.hives.gfctl.domain = "g.t.local"; }; hiveNamedAfterOtelOidcSubject = hive { deploy.swarm-otel.enable = false; deploy.bao.otelOidcCommonName = "otctl"; swarm.hives.otctl.domain = "o.t.local"; }; # 🩸 A different shape from every fixture above: the matrix-token and # queue-credential roles are written PER HIVE, so the subject a hive must not # be is `-` rather than the prefix itself. Reserving # only the prefix would leave the composed spelling free, and a hive taking it # would present a leaf the other hive's role accepts — which is a hive reading # another hive's queue credential, the exact widening the split exists to # avoid. # # Two hives here, not one: the collision is with the OTHER hive's role. hiveNamedAfterPerHiveReaderSubject = hive { deploy.swarm-otel.enable = false; deploy.bao.queueAgentCommonNamePrefix = "qr"; swarm.hives.other.domain = "o.t.local"; swarm.hives.qr-other.domain = "q.t.local"; }; hiveNameWithComposedWord = hive { deploy.swarm-otel.enable = false; swarm.hives."h1-agent".domain = "a.t.local"; }; # Markers from `lib/name-guards.nix`'s two `problem` strings. Matching the # problem rather than the `why` prose keeps the messages rewordable. equalityGuardFired = h: lib.any (a: !a.assertion && lib.hasInfix "has reserved name(s)" a.message) h.assertions; fragmentGuardFired = h: lib.any ( a: !a.assertion && lib.hasInfix "has name(s) containing a reserved word" a.message ) h.assertions; cases = [ { # `ctl` is in no deny list — it is reserved *because it is the subject a # cert-auth role accepts*, which is a value an operator sets, so a # literal deny entry could never have covered it. name = "a hive named after a cert-auth subject is refused, with the collector off"; ok = equalityGuardFired hiveNamedAfterCertSubject && lib.any (a: !a.assertion && lib.hasInfix "'ctl'" a.message) hiveNamedAfterCertSubject.assertions; } { # Every cert-auth subject is reserved, not just the first one in the # list. Without this case the second element could be dead and the case # above would still pass. name = "a hive named after the secret publisher's subject is refused too"; ok = equalityGuardFired hiveNamedAfterPublisherSubject && lib.any ( a: !a.assertion && lib.hasInfix "'pubctl'" a.message ) hiveNamedAfterPublisherSubject.assertions; } { # And the third, for the reason the second one's comment gives one list # element earlier. `certAuthCns` is where a role added beside the others # has to register itself, and nothing but a case per element notices when # one forgets. name = "a hive named after matrix-ctl's subject is refused too"; ok = equalityGuardFired hiveNamedAfterMatrixCtlSubject && lib.any ( a: !a.assertion && lib.hasInfix "'mintctl'" a.message ) hiveNamedAfterMatrixCtlSubject.assertions; } { # The fourth and fifth, for the reason the case above gives: `certAuthCns` # is where a role added beside the others registers itself, and nothing # but a case per element notices when one forgets. These two are the # subjects of the readers that fetch Grafana's and the collector's OIDC # client secrets. name = "a hive named after either OIDC-secret reader's subject is refused too"; ok = equalityGuardFired hiveNamedAfterGrafanaOidcSubject && lib.any ( a: !a.assertion && lib.hasInfix "'gfctl'" a.message ) hiveNamedAfterGrafanaOidcSubject.assertions && equalityGuardFired hiveNamedAfterOtelOidcSubject && lib.any ( a: !a.assertion && lib.hasInfix "'otctl'" a.message ) hiveNamedAfterOtelOidcSubject.assertions; } { # 🩸 The per-hive half, and the one a prefix-only reservation would miss: # the role is `-`, so the reserved string has to be composed # against every declared hive. Here hive `qr-other` collides with the role # written for hive `other` — a leaf that reads a credential belonging to a # hive that is not it. name = "a hive named after another hive's per-hive reader subject is refused"; ok = equalityGuardFired hiveNamedAfterPerHiveReaderSubject && lib.any ( a: !a.assertion && lib.hasInfix "'qr-other'" a.message ) hiveNamedAfterPerHiveReaderSubject.assertions; } { # Without this the case above proves nothing: an arm that fires for every # roster is not a guard, and `hives` is non-empty in both fixtures. name = "a legal hive roster trips neither name guard"; ok = !(equalityGuardFired hiveNamesAllLegal) && !(fragmentGuardFired hiveNamesAllLegal); } { # The substring guard came along in the move and has to still work. # `h1-agent` mints exactly the client id hive `h1`'s agents present. name = "a hive name containing a composed-identifier word is refused, with the collector off"; ok = fragmentGuardFired hiveNameWithComposedWord; } { # ⚠️ The control that makes "with the collector off" mean anything. If a # fixture silently had swarm-otel enabled, all three cases above would # pass while testing the arrangement they exist to rule out. name = "the guard fixtures really do have the collector disabled"; ok = !hiveNamedAfterCertSubject.services.hyperhive.deploy.swarm-otel.enable && !hiveNamesAllLegal.services.hyperhive.deploy.swarm-otel.enable && !hiveNameWithComposedWord.services.hyperhive.deploy.swarm-otel.enable; } ]; in runGroup "name-guards" cases