From 7d24feaf25c0c61718bdf155537f4cdb1639bd6e Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 14 Oct 2026 10:18:40 +0200 Subject: [PATCH] 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 --- nix/host-modules/swarm-controller.nix | 11 ++++++++++- nix/module-eval/bao-controller.nix | 6 ++++++ swarmctl/README.md | 5 ++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/nix/host-modules/swarm-controller.nix b/nix/host-modules/swarm-controller.nix index 9efcf00f..da205d18 100644 --- a/nix/host-modules/swarm-controller.nix +++ b/nix/host-modules/swarm-controller.nix @@ -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}" diff --git a/nix/module-eval/bao-controller.nix b/nix/module-eval/bao-controller.nix index e74333ec..99fa88b5 100644 --- a/nix/module-eval/bao-controller.nix +++ b/nix/module-eval/bao-controller.nix @@ -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 = diff --git a/swarmctl/README.md b/swarmctl/README.md index f7c0754e..566ab2ac 100644 --- a/swarmctl/README.md +++ b/swarmctl/README.md @@ -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.