Each container now runs the journal forwarder the queue's and the builder's containers run, pushing to a receiver of its own on the swarm collector. Each authenticates as a new machine OIDC client of its own (swarm.<service>.otel.clientId), registered by a glue file wherever authelia runs, minted by authelia and published to the store by the secret publisher. Grafana's browser login client is unchanged. The fetch unit inside each container reads the client secret with the store identity the host already holds for that service (the runner's, Grafana's, and the log store responder's), now also handed into the container as an nspawn system credential. Each of those three policies gains one read stanza on its forwarder's client path. The host units that use those identities are unchanged. The three containers drop --link-journal=host, and the host collector stops reading them.
136 lines
5.3 KiB
Nix
136 lines
5.3 KiB
Nix
# The swarm's metrics UI as every hive sees it: the name it is served under
|
||
# and the OIDC client it is registered as, identical on every host. What the
|
||
# host running it decides, and the container itself, are in
|
||
# ./swarm-grafana.nix.
|
||
{
|
||
lib,
|
||
config,
|
||
...
|
||
}:
|
||
let
|
||
cfg = config.services.hyperhive.swarm.grafana;
|
||
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
|
||
{
|
||
# `enable` is `services.hyperhive.deploy.grafana.enable` — see
|
||
# ./deploy.nix. Whether this host runs the swarm's Grafana is a
|
||
# deployment decision, and `swarm.*` has to be identical on every host.
|
||
# Here is what the service IS to every hive: the name it is served under
|
||
# and the OIDC client it is registered as. What the host running it
|
||
# decides — which build it runs, where its datasources point, which plugins
|
||
# are in its store path, and the socket directory it shares with nginx — is
|
||
# `deploy.grafana`, in ./swarm-grafana.nix.
|
||
options.services.hyperhive.swarm.grafana = {
|
||
machine = lib.mkOption {
|
||
type = lib.types.str;
|
||
readOnly = true;
|
||
default = "swarm-grafana";
|
||
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.
|
||
'';
|
||
};
|
||
|
||
otel.clientId = lib.mkOption {
|
||
type = lib.types.str;
|
||
readOnly = true;
|
||
default = "swarm-grafana-collector";
|
||
description = ''
|
||
OAuth2 client id the journal forwarder inside Grafana's container
|
||
authenticates as, and the audience it asks its token for. Its own,
|
||
not {option}`services.hyperhive.swarm.grafana.oidc.clientId`, which
|
||
is the browser login's.
|
||
|
||
Read-only because three hosts have to agree on it: authelia
|
||
registers the client (`glue-grafana-otel-oidc-client.nix`), the
|
||
swarm collector admits it on the container's receiver
|
||
(`swarm.otel.serviceProducers`), and the secret publisher carries
|
||
its secret to the store.
|
||
'';
|
||
};
|
||
|
||
domain = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "grafana.${domainBase}";
|
||
defaultText = lib.literalExpression ''"grafana.''${services.hyperhive.swarm.domain}"'';
|
||
description = ''
|
||
Name the gateway serves this on. A sibling of the swarm's other
|
||
service names, so the swarm-services sub-CA can issue for it — see
|
||
`hive-tls.nix` for why a service name being a sibling rather than a
|
||
child decides which CA may sign it.
|
||
|
||
⚠️ Changing this changes the OAuth redirect URI, which authelia
|
||
matches exactly. Both sides move together because both derive from
|
||
this option; an operator who pins one by hand breaks the login.
|
||
|
||
The swarm collector scrapes Grafana's own metrics at
|
||
`https://<this name>/metrics`, from whichever host it runs on.
|
||
Grafana serves that endpoint to anyone, so the gateway puts it
|
||
behind authelia, whose rules admit only the collector's OAuth2
|
||
client there: see `publishedScrapeTargets` in
|
||
./swarm-otel-service.nix.
|
||
'';
|
||
};
|
||
|
||
oidc = {
|
||
clientId = lib.mkOption {
|
||
type = lib.types.str;
|
||
default = "swarm-grafana";
|
||
description = ''
|
||
The authelia OIDC client id. Names the application rather than
|
||
the protocol, per the convention in
|
||
{option}`services.hyperhive.swarm.authelia.oidc.clients`.
|
||
'';
|
||
};
|
||
|
||
redirectUri = lib.mkOption {
|
||
type = lib.types.str;
|
||
readOnly = true;
|
||
default = "https://${cfg.domain}/login/generic_oauth";
|
||
defaultText = lib.literalExpression ''"https://''${services.hyperhive.swarm.grafana.domain}/login/generic_oauth"'';
|
||
description = ''
|
||
OAuth callback authelia sends the browser back to, and the URI it
|
||
matches **exactly**.
|
||
|
||
Read-only, like {option}`services.hyperhive.swarm.grafana.machine`
|
||
and for the same reason: Grafana derives it from its own
|
||
`root_url` (`<root_url>/login/generic_oauth`), so it is a fact
|
||
other modules may read rather than a knob. The glue that registers
|
||
this client wherever authelia runs reads it from here instead of
|
||
restating the format — a second spelling of it is a silently
|
||
rejected login.
|
||
'';
|
||
};
|
||
|
||
role = lib.mkOption {
|
||
type = lib.types.enum [
|
||
"Viewer"
|
||
"Editor"
|
||
"Admin"
|
||
];
|
||
default = "Admin";
|
||
example = "Editor";
|
||
description = ''
|
||
Grafana org role every SSO user is assigned.
|
||
|
||
`Admin` by default, and that is a considered default rather than
|
||
a permissive one: the login form is disabled whenever SSO is
|
||
configured, so this is the *only* way anyone reaches Grafana —
|
||
a `Viewer` default would produce a swarm nobody can administer.
|
||
Passing authelia already means being an operator of this swarm;
|
||
its user store is the small, `swarmctl`-managed one.
|
||
|
||
Lower it if a swarm ever grows read-only operators, which is a
|
||
one-line change here.
|
||
'';
|
||
};
|
||
};
|
||
|
||
};
|
||
}
|