# The swarm's secret store as every hive sees it: the names it is reached on, # its port, its container, and the client ids it is registered under, # identical on every host. What the host running it decides, and the # container itself, are in ./swarm-bao.nix. { lib, config, ... }: let cfg = config.services.hyperhive.swarm.bao; swarmDomain = config.services.hyperhive.swarm.domain; # Total on a null swarm domain for the same reason every sibling module is: # the required-domain assertion in hive-network.nix should be what an operator # sees, not a coercion error from here. domainBase = if swarmDomain == null then "invalid" else swarmDomain; in { options.services.hyperhive.swarm.bao = { machine = lib.mkOption { type = lib.types.str; readOnly = true; default = "swarm-bao"; description = '' Container name. Read-only: the name appears in host paths and in `machinectl`, so it is a fact other modules may read rather than a knob. ''; }; domain = lib.mkOption { type = lib.types.str; default = "bao.${domainBase}"; defaultText = lib.literalExpression ''"bao.''${services.hyperhive.swarm.domain}"''; description = '' Name the store is reached on. A **sibling** of the swarm's other service names, not a child of any hive domain: an authority whose `nameConstraints` permit one hive's domain cannot issue for a sibling of it, so the shape of this name decides which authorities could ever sign for the store. That is a property of the name, not a choice of issuer — this module makes no such choice. ''; }; ui.domain = lib.mkOption { type = lib.types.str; default = "bao-ui.${domainBase}"; defaultText = lib.literalExpression ''"bao-ui.''${services.hyperhive.swarm.domain}"''; description = '' Name the gateway serves the store's browser UI on, to members of authelia's `admins` group only. Swarm-wide because authelia's host writes the access rule for it and the store's host serves it. Must differ from {option}`services.hyperhive.swarm.bao.domain`: that name is the mutual-TLS endpoint every reader dials, and it has no vhost. ''; }; ui.oidc.clientId = lib.mkOption { type = lib.types.str; readOnly = true; default = "swarm-bao-ui"; description = '' OAuth2 client id the store's `oidc` auth method logs browser users in as, at authelia. Swarm-wide and read-only because two hosts have to agree on it: authelia registers the client (`glue-bao-ui-oidc-client.nix`) and mints its secret, and the store's host reads that secret back out of the store under a path composed from this id. ''; }; ui.oidc.redirectUri = lib.mkOption { type = lib.types.str; readOnly = true; default = "https://${cfg.ui.domain}/ui/vault/auth/oidc/oidc/callback"; defaultText = lib.literalExpression ''"https://''${services.hyperhive.swarm.bao.ui.domain}/ui/vault/auth/oidc/oidc/callback"''; description = '' Where authelia sends the browser back to after an OIDC login, and the URI both authelia and the store's `oidc` role match **exactly**. The format is the OpenBao UI's own route, `/ui/vault/auth//oidc/callback`, with the mount `oidc`. The UI composes it from the page's origin, so it only matches when the gateway serves the UI on port 443. ''; }; port = lib.mkOption { type = lib.types.port; default = 8200; description = '' TCP port the store listens on. Upstream's own default, kept so an operator reading OpenBao documentation finds what they expect. Swarm-wide because a client has to know it to reach the store, and the same port on every listener: which *addresses* the store answers on is the running host's business ({option}`services.hyperhive.deploy.bao.extraListenAddresses`), but which port it answers on is something the whole swarm agrees. ''; }; otel.clientId = lib.mkOption { type = lib.types.str; readOnly = true; default = "swarm-bao-collector"; description = '' OAuth2 client id the collector inside the store's container authenticates as, and — self-referentially, the shape every hive's client already uses — the audience it asks its token for. **Its own, not the swarm collector's and not `swarm-controller`'s.** One identity per principal: this forwarder runs wherever the store runs, which is not where either of those two runs, and the receiver it pushes to (`swarm.otel.storeProducerName`) admits this id alone. Swarm-wide and read-only because three hosts have to agree on it: authelia registers the client (`glue-swarm-bao-otel-oidc-client.nix`), the swarm collector checks the audience (`swarm-otel.nix`), and the store's host reads the minted secret back out of the store under a path composed from it. Two spellings present as a healthy-looking 401. ''; }; }; }