Watch
0
0
Fork
You've already forked hyperhive
0

swarmctl: on a controller host, trust the swarm's root without SWARMCTL_CA_FILE

`swarmctl login` (and any `--swarm` verb) on a controller host failed TLS
with `UnknownIssuer` against the swarm's own gateway: its leaf chains to
the store's `pki` root, which no system store holds, and the only way
through was `--ca-file`/`SWARMCTL_CA_FILE` by hand.

The host's swarmctl wrapper now sets `SSL_CERT_FILE` to the bundle the
swarm-controller daemon already trusts (`/run/swarm-controller-ca/
trust-bundle.pem`: system CAs plus the swarm's root), when the gateway is
self-signed. swarmctl's reqwest/rustls resolves roots the same way the
daemon's does, so this is the path the daemon uses to reach the same
gateway. `--set` rather than a default, since a nix shell exports its own
`SSL_CERT_FILE`.

Pointing `SWARMCTL_CA_FILE` at the bundle instead would not work (#5243):
`--ca-file` adds each certificate as a root and fails the whole client on
one it can't parse.

Hosts without the controller (an operator laptop) still need `--ca-file`.

Refs #5239
Fixes #5243
This commit is contained in:
atlas 2026-10-14 10:18:40 +02:00 • committed by mara
commit 7d24feaf25
3 changed files with 20 additions and 2 deletions

View file

@ -159,6 +159,15 @@ let
gatewayCfg = config.services.hyperhive.gateway;
};
# `swarmctl --swarm` (and `login`) reach the same gateway as the daemon,
# through the same `reqwest`/`rustls` roots, so the wrapper hands it the
# daemon's bundle. `--set`, not a default: a nix shell exports its own
# `SSL_CERT_FILE`, which would win and lack the swarm's root. The bundle
# holds the system CAs too, so public hosts still verify.
swarmctlTrustEnv = lib.optionalAttrs caTrust.useSelfSigned {
SSL_CERT_FILE = caTrust.bundlePathFor "swarm-controller";
};
# The controller's own OAuth2 client. It is NOT a hive: the per-hive
# clients the roster issues belong to hives, and the responder's client
# belongs to the responder. One identity per principal — the rule is that
@ -257,7 +266,7 @@ let
wrapProgram $out/bin/swarmctl ${
lib.concatStringsSep " " (
lib.mapAttrsToList (name: value: "--set ${name} ${lib.escapeShellArg value}") (
controllerSocketEnv // deployCfg.swarm-controller.swarmctlEnvironment
controllerSocketEnv // swarmctlTrustEnv // deployCfg.swarm-controller.swarmctlEnvironment
)
++ lib.mapAttrsToList (
name: value: "--set-default ${name} ${lib.escapeShellArg value}"

View file

@ -146,6 +146,12 @@ let
&& lib.hasInfix "--set-default SWARMCTL_LOGIN_DEFAULT_SWARM https://t.local" swarmctlWrapperCmd
&& !(lib.hasInfix "SWARMCTL_SWARM" swarmctlWrapperCmd);
}
{
# The gateway's leaf chains to the store's root, which no system store
# holds; without the bundle `login` fails `UnknownIssuer`.
name = "the host's swarmctl trusts the swarm's root through the daemon's bundle";
ok = lib.hasInfix "--set SSL_CERT_FILE /run/swarm-controller-ca/trust-bundle.pem" swarmctlWrapperCmd;
}
{
name = "a swarm with no matrix gateway vhost gives the controller no homeserver variable";
ok =

View file

@ -189,5 +189,8 @@ refuses it. When it can't be refreshed, any verb says to run `swarmctl login`
again. To log out, delete the file.
A swarm whose gateway serves a certificate from the swarm's own CA needs
that CA: `--ca-file` or `SWARMCTL_CA_FILE`. The `user` verbs edit the local
that CA. On a controller host the swarm-controller module's wrapper sets
`SSL_CERT_FILE` to the bundle the daemon itself trusts (the system CAs plus
the swarm's root), so nothing needs setting there. Elsewhere, pass the CA
with `--ca-file` or `SWARMCTL_CA_FILE`. The `user` verbs edit the local
users file and have no remote form.