# Glue: register the swarm controller as an OIDC client wherever authelia runs. # # ONE PAIRING PER FILE — swarm-controller ← authelia, and nothing else. Deleting # this leaves a controller whose token requests authelia answers with a 401, and # no minted secret for swarm-secret-publish to put in the store. # # ⚠️ Gated on authelia being HERE, and NOT on this host running the controller. # A client is a row in THIS host's provider config, so it is declared where that # config is rendered; ./swarm-controller.nix's `config` hangs off # `deploy.swarm-controller.enable`, which answers a question about a different # host. Same shape as ./glue-grafana-oidc-client.nix. # # Registered whether or not the swarm runs a controller: nothing in `swarm.*` # records that, so the cost of a swarm without one is one unused client and one # unused minted secret. { lib, config, ... }: let hyperhiveCfg = config.services.hyperhive; deployCfg = hyperhiveCfg.deploy; queueClientId = hyperhiveCfg.swarm.controller.queueClientId; in { config = lib.mkIf deployCfg.authelia.enable { # One declaration, two readers: the id is the read-only option the # controller authenticates under, so a mismatch cannot be introduced here. services.hyperhive.swarm.authelia.oidc.clients = [ { id = queueClientId; description = "HyperHive swarm controller"; # `client_credentials`: a daemon authenticating as itself, with nobody # to redirect. Declared rather than inferred from an empty redirect # list, because authelia permits only the grants a client names and an # omitted `grant_types` means authorization-code alone. kind = "machine"; # Its own id as its own permitted audience. The controller mints a # token for `SWARM_CONTROLLER_OTEL_AUDIENCE` (./glue-controller-env.nix), # and ./swarm-otel.nix's `oidc/swarm` authenticator checks for this # same id; either half without the other is a 401 several layers from # this line. audience = [ queueClientId ]; } ]; }; }