# `checks.module-eval-nats-authelia` — 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 queue's callout identity, fourth split slice. `autoGenerateCallout` is # left FALSE on purpose: that is what makes the seed paths the thing deciding # `responderConfigured`, so the assertion below is about the seeds rather # than about the auto-mint branch. Every one of the seven old paths is # defined — `enable` included, which is why it is spelled the old way here # while the fixture below uses the new one — so dropping any single nats # shim fails the eval, not just the arms read. natsOldPath = hive { swarm.nats.enable = true; swarm.nats.autoGenerateCallout = false; swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutUserSeedFile = "/run/secrets/nats-user.seed"; swarm.nats.calloutIssuerSeedFile = "/run/secrets/nats-issuer.seed"; swarm.nats.authPackage = pkgs.emptyDirectory; }; # Seventh split slice, plus slice 10's two authelia packages. Of slice 7's # movers only `usersFile` has a rename entry — the other two are `readOnly`, # and a rename module contributes a definition, which a read-only option # refuses; see ./host-modules/deploy.nix. `package` and `bridgePackage` are # ordinary options, so they do carry one. The two arms # below have different jobs. `usersFile` tests the rename; the nats one tests # that a reader repointed to the new namespace still renders the derived # path, which is the failure this slice could actually have shipped — seven # of those reads went through an alias a path-shaped grep cannot see. autheliaOldPath = hive { deploy.authelia.enable = true; deploy.nats.enable = true; swarm.authelia.usersFile = "/var/lib/test-authelia/users.yml"; swarm.authelia.package = pkgs.emptyDirectory; swarm.authelia.bridgePackage = pkgs.emptyDirectory; swarm.nats.autoGenerateCallout = false; swarm.nats.calloutUserPublicKey = "UTESTUSERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutIssuerPublicKey = "ATESTISSUERPUBKEYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; swarm.nats.calloutUserSeedFile = "/run/secrets/nats-user.seed"; swarm.nats.calloutIssuerSeedFile = "/run/secrets/nats-issuer.seed"; }; # A hive running NOTHING of the swarm's own services — no IdP here, no # `swarm.authelia.url` set by hand. The whole point of the fixture is what it # does *not* say: it is the shape whose IdP address used to be null, and # before that a co-location-derived guess. autheliaNotColocated = hive { }; # The same swarm's IdP host, for the arm that the two agree. autheliaColocated = hive { deploy.authelia.enable = true; }; cases = [ { # Reads the RENDERED settings, not the option: `calloutBlocks {…} // { # … }` is a shallow merge, and a future edit that dropped or shadowed # this key would still evaluate cleanly — the only reader that would # notice is a publisher whose row exceeds upstream's much smaller # default, and by then it is a dropped row, not an eval failure. # Piggybacks on the pre-rename nats fixture above, which already # renders this container's full config. name = "the queue's payload ceiling is set, not inherited from the server's default"; ok = natsOldPath.containers.swarm-nats.config.services.nats.settings.max_payload == 8388608; } { # Reads the RENDERED unit text, not the module's source, because the # failure this defends against renders perfectly: systemd substitutes # `$NAME` in `ExecStart` regardless of quoting, so a single dollar # here hands the responder `.term.{hive}.>` — a grant that parses, is # accepted, and matches nothing an agent ever publishes to. Asserting # the doubled dollar is the only way to tell the two apart before # deploy. The flag's presence is asserted separately so that dropping # the grant entirely fails as its own arm rather than as an escaping # complaint. name = "the responder grants agents their hive's terminal subject, and the dollar survives systemd"; ok = let exec = natsOldPath.containers.swarm-nats.config.systemd.services.swarm-nats-auth.serviceConfig.ExecStart; in lib.hasInfix "--agent-publish-subject " exec && lib.hasInfix "$$SWARM.term.{hive}.>" exec; } { # Second grant, same escaping trap, asserted separately: the two # subject families are independent features (terminal rows and the # turn-state header) and dropping either should fail as its own arm # rather than being masked by the other still being present. # # Flag and argument are matched as one infix rather than as two # independent `hasInfix` calls: the responder takes the flag # repeatedly, so the thing worth pinning is that THIS subject is the # argument of one of them, which two separate presence checks would # both pass on while the subject sat under some other flag entirely. name = "the responder grants agents their hive's agent-state subject too"; ok = let exec = natsOldPath.containers.swarm-nats.config.systemd.services.swarm-nats-auth.serviceConfig.ExecStart; in lib.hasInfix "--agent-publish-subject '$$SWARM.agent-state.{hive}.>'" exec; } { # Not a rename test. `hostClientSecretDir` is `readOnly`, so the fixture # cannot define it; what can break is a reader left pointing at the # namespace it moved out of. Five modules read this through an # `autheliaCfg` alias, where a path-shaped grep does not see it. name = "a consumer of authelia's host client-secret dir renders it from the deploy namespace"; ok = lib.hasInfix "/var/lib/nixos-containers/swarm-authelia/var/lib/authelia-swarm/oidc-clients/" autheliaOldPath.systemd.services.swarm-nats-auth-secrets.script; } { # Config NAMES the IdP; it never computes where the IdP is. Both arms # matter together: the address is the swarm's name on a hive that runs # nothing, and it is the SAME string on the hive that serves the vhost — # so a re-introduced co-location branch shows up as the two disagreeing # rather than as a value that merely looks plausible on one of them. name = "the swarm IdP address is its domain on every hive, co-located or not"; ok = autheliaNotColocated.services.hyperhive.swarm.authelia.url == "https://${autheliaNotColocated.services.hyperhive.swarm.authelia.domain}" && autheliaColocated.services.hyperhive.swarm.authelia.url == autheliaNotColocated.services.hyperhive.swarm.authelia.url; } { # …and the name it resolves through is a DOMAIN, not a host this config # picked. `127.0.0.1` is what the old default rendered on the IdP host, # the bridge address is what the queue's defaults still render, and # neither is an address a client may be handed: the domain is allowed to # resolve differently in different places, which is the whole property. name = "the swarm IdP address names no host address"; ok = let url = autheliaNotColocated.services.hyperhive.swarm.authelia.url; bridgeIp = autheliaNotColocated.services.hyperhive.network.bridgeIp; in !(lib.hasInfix "127.0.0.1" url) && !(lib.hasInfix "localhost" url) && !(lib.hasInfix bridgeIp url); } ]; in runGroup "nats-authelia" cases