Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/nix/host-modules/swarm-bao-service.nix

130 lines
5.1 KiB
Nix

# 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/<mount>/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.
'';
};
};
}