hive-tls: move the controllers' CA env settings into glue
This commit is contained in:
parent
592ac8a5f2
commit
e21a03d6ff
7 changed files with 85 additions and 41 deletions
|
|
@ -37,6 +37,7 @@
|
|||
./glue-grafana-oidc-client.nix
|
||||
./glue-hive-otel-authelia.nix
|
||||
./glue-hive-tls-bao-identity.nix
|
||||
./glue-hive-tls-controllers.nix
|
||||
./glue-logs-auth-bao-identity.nix
|
||||
./glue-metrics-auth-bao-identity.nix
|
||||
./glue-matrix-ctl-bao-identity.nix
|
||||
|
|
|
|||
55
nix/host-modules/glue-hive-tls-controllers.nix
Normal file
55
nix/host-modules/glue-hive-tls-controllers.nix
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# Glue: hand the hive's trust bundle to the daemons that dial the swarm over TLS.
|
||||
#
|
||||
# ONE PAIRING PER FILE — hive-c0re and the swarm controller ← the trust bundle
|
||||
# ./hive-tls.nix assembles. Deleting this leaves daemons that trust only the
|
||||
# platform roots: hive-c0re's lifecycle cannot hand agents the bundle, and the
|
||||
# swarm-queue clients die at `invalid peer certificate: UnknownIssuer` against
|
||||
# an authelia signed by the swarm CA.
|
||||
#
|
||||
# `HIVE_TLS_CA_PATH` signals the hive-c0re lifecycle that a trust bundle exists:
|
||||
# it bind-mounts the file (read-only, public certs ONLY — never a key) into each
|
||||
# agent container, and the meta flake wires the per-agent trust bundle.
|
||||
# `*_OIDC_CA_FILE` names the same anchor for the swarm-queue clients that mint a
|
||||
# token from authelia over TLS. `swarm-queue-client` builds a bare
|
||||
# `reqwest::Client`, so it trusts only the platform roots without it. Where the
|
||||
# gateway is not self-signed ./hive-tls.nix contributes no bundle and nothing is
|
||||
# set here, which is correct for a swarm fronted by a public certificate.
|
||||
#
|
||||
# ⚠️ One gate per daemon, each ANDed with the bundle existing: the gateway also
|
||||
# runs on hosts with no hive and no controller, and defining an environment key
|
||||
# on a unit that does not exist CREATES a unit fragment for it — inert (no
|
||||
# `ExecStart`, empty `wantedBy`, never activated), and it evaluates and builds
|
||||
# clean.
|
||||
#
|
||||
# ⚠️ Files like this are the only place a `deploy.<foo>` value may derive from
|
||||
# a `deploy.<bar>.enable`. Everywhere else that is forbidden.
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
hyperhiveCfg = config.services.hyperhive;
|
||||
deployCfg = hyperhiveCfg.deploy;
|
||||
gatewayCfg = hyperhiveCfg.gateway;
|
||||
|
||||
# ./hive-tls.nix writes the bundle only where the gateway serves its own
|
||||
# self-signed cert.
|
||||
haveBundle = gatewayCfg.enable && gatewayCfg.useSelfSigned;
|
||||
trustBundle = "${deployCfg.hive-controller.tls.stateDir}/trust-bundle.pem";
|
||||
in
|
||||
{
|
||||
config = lib.mkIf haveBundle (
|
||||
lib.mkMerge [
|
||||
(lib.mkIf deployCfg.hive-controller.enable {
|
||||
systemd.services.hive-c0re.environment = {
|
||||
HIVE_TLS_CA_PATH = trustBundle;
|
||||
HIVE_C0RE_OIDC_CA_FILE = trustBundle;
|
||||
};
|
||||
})
|
||||
(lib.mkIf deployCfg.swarm-controller.enable {
|
||||
systemd.services.swarm-controller.environment.SWARM_CONTROLLER_OIDC_CA_FILE = trustBundle;
|
||||
})
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ in
|
|||
# gateway vhost, which serves a leaf chaining to the store's pki root —
|
||||
# a CA generated at runtime, which no build-time trust store can
|
||||
# contain. The queue's TLS is verified against `HIVE_C0RE_OIDC_CA_FILE`
|
||||
# (../hive-tls.nix), not this bundle.
|
||||
# (../glue-hive-tls-controllers.nix), not this bundle.
|
||||
#
|
||||
# ⚠️ `SSL_CERT_FILE` REPLACES the trust store, and unlike the other
|
||||
# consumers of this helper, hive-c0re already makes a *public*-TLS call:
|
||||
|
|
|
|||
|
|
@ -929,41 +929,5 @@ in
|
|||
Persistent = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Signal the hive-c0re lifecycle that a trust bundle exists: it bind-mounts
|
||||
# this file (read-only, public certs ONLY — never a key) into each
|
||||
# agent container so agents + their tools can trust the gateway's
|
||||
# self-signed leaf, and the meta flake wires the per-agent trust
|
||||
# bundle. No key path is ever exposed (an agent that could read one
|
||||
# could mint trusted certs).
|
||||
#
|
||||
# ⚠️ Every line here that names another daemon's unit is gated on that
|
||||
# daemon's own enable: the gateway also runs on hosts with no hive and no
|
||||
# controller, and defining an environment key on a unit that does not
|
||||
# exist CREATES a unit fragment for it — inert (no `ExecStart`, empty
|
||||
# `wantedBy`, never activated), and it evaluates and builds clean.
|
||||
systemd.services.hive-c0re.environment.HIVE_TLS_CA_PATH =
|
||||
lib.mkIf hyperhiveCfg.deploy.hive-controller.enable "${cfg.stateDir}/trust-bundle.pem";
|
||||
|
||||
# The same anchor, named for the swarm-queue clients that need it when
|
||||
# they mint a token from authelia over TLS. Declared HERE, beside the
|
||||
# bundle, rather than in each consumer's module: the path is this
|
||||
# module's fact, and two consumers re-deriving `${stateDir}/…` would be
|
||||
# two places to fix when it changes.
|
||||
#
|
||||
# ⚠️ `swarm-queue-client` builds a bare `reqwest::Client`, so it trusts
|
||||
# only the platform roots and dies at `invalid peer certificate:
|
||||
# UnknownIssuer` against a swarm whose authelia is signed by the swarm
|
||||
# CA. The bundle is already assembled on disk and handed to hive-c0re
|
||||
# under a different variable name; this declaration is what points the
|
||||
# queue client at it.
|
||||
#
|
||||
# Where the gateway is not self-signed this module contributes nothing, and
|
||||
# the clients then fall back to the platform roots — which is correct for
|
||||
# a swarm fronted by a public certificate.
|
||||
systemd.services.hive-c0re.environment.HIVE_C0RE_OIDC_CA_FILE =
|
||||
lib.mkIf hyperhiveCfg.deploy.hive-controller.enable "${cfg.stateDir}/trust-bundle.pem";
|
||||
systemd.services.swarm-controller.environment.SWARM_CONTROLLER_OIDC_CA_FILE =
|
||||
lib.mkIf hyperhiveCfg.deploy.swarm-controller.enable "${cfg.stateDir}/trust-bundle.pem";
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1011,7 +1011,7 @@ let
|
|||
# controller, so the host that runs it says so itself.
|
||||
#
|
||||
# Probed by what the daemon needs in order to run, not by
|
||||
# `? swarm-controller`: ./host-modules/hive-tls.nix defines an
|
||||
# `? swarm-controller`: ./host-modules/glue-hive-tls-controllers.nix defines an
|
||||
# environment key on that unit name, which leaves the attr
|
||||
# present-but-inert (no `ExecStart`, empty `wantedBy`) on every hive
|
||||
# that has a CA — see the comment there. The daemon's system user has no
|
||||
|
|
|
|||
|
|
@ -684,6 +684,30 @@ let
|
|||
&& (t.timerConfig.Unit or "hive-tls-resign.service") == "hive-tls-resign.service"
|
||||
&& !(remainsAfterExit leafUnit);
|
||||
}
|
||||
{
|
||||
# ./glue-hive-tls-controllers.nix. The controllers are gated apart: the
|
||||
# hive host without the swarm controller sets the first two only, and a
|
||||
# gateway with neither sets none. The first arm is the presence control.
|
||||
name = "the trust bundle reaches each controller's environment only where that controller runs";
|
||||
ok =
|
||||
let
|
||||
bundle = "/var/lib/hive-tls/trust-bundle.pem";
|
||||
envOf = m: unit: m.systemd.services.${unit}.environment or { };
|
||||
hiveVars = [
|
||||
"HIVE_TLS_CA_PATH"
|
||||
"HIVE_C0RE_OIDC_CA_FILE"
|
||||
];
|
||||
anyOf =
|
||||
m: unit: vars:
|
||||
lib.any (v: (envOf m unit) ? ${v}) vars;
|
||||
in
|
||||
lib.all (v: (envOf allLocal "hive-c0re").${v} == bundle) hiveVars
|
||||
&& (envOf allLocal "swarm-controller").SWARM_CONTROLLER_OIDC_CA_FILE == bundle
|
||||
&& lib.all (v: (envOf noService "hive-c0re").${v} == bundle) hiveVars
|
||||
&& !(anyOf noService "swarm-controller" [ "SWARM_CONTROLLER_OIDC_CA_FILE" ])
|
||||
&& !(anyOf gatewayNoHive "hive-c0re" hiveVars)
|
||||
&& !(anyOf gatewayNoHive "swarm-controller" [ "SWARM_CONTROLLER_OIDC_CA_FILE" ]);
|
||||
}
|
||||
{
|
||||
# A oneshot has no start timeout by default, so an attempt that never
|
||||
# ends never fails into a retry.
|
||||
|
|
|
|||
Loading…
Reference in a new issue