swarm-bao: put a wrapped bao on the host, with this store's coordinates

Reading a role out of the store took four round-trips of environment
guessing: the certificate has no IP SAN so loopback cannot verify, the
DNS name resolves to the bridge from inside the container, and the CA
the client needs lives on the host, which had no `bao` at all.

The wrapper carries the address, the server CA and — where the PKI glue
minted one — the reader's own leaf, so cert-auth login needs nothing
typed. `--set-default` throughout: an operator pointing BAO_ADDR
elsewhere still wins.

Only the wrapper reaches PATH. `wrapProgram` renames the real binary,
so there is no unwrapped `bao` to reach by accident, and the module-eval
case asserting the package's absence is what keeps a later "install it
too" from undoing that.
This commit is contained in:
atlas 2026-09-10 14:49:29 +02:00
commit c24dd03485
2 changed files with 85 additions and 0 deletions

View file

@ -219,6 +219,39 @@ let
clientCaPath = "${tlsDir}/client-ca.pem";
# The store's own CLI, on the host, with this deployment's coordinates already
# in the environment. Only the wrapper goes on `PATH` — `wrapProgram` renames
# the real binary, so there is no unwrapped `bao` to reach by accident and
# produce a connection that names no cause.
#
# `--set-default`, not `--set`: an operator pointing `BAO_ADDR` at another
# store must win. A wrapper that overrode an explicit environment would be the
# same class of surprise it exists to remove.
baoCli =
let
# The reader's identity `glue-bao-tls` already mints on this host, so
# cert-auth login needs nothing typed either. A token stays the
# operator's to supply — a wrapper cannot conjure one.
wrapArgs = [
"--set-default BAO_ADDR ${lib.escapeShellArg "https://${cfg.domain}:${toString cfg.port}"}"
]
++ lib.optional (
baoDeploy.serverCaFile != null
) "--set-default BAO_CACERT ${lib.escapeShellArg baoDeploy.serverCaFile}"
++ lib.optionals (baoDeploy.clientCertFile != null && baoDeploy.clientKeyFile != null) [
"--set-default BAO_CLIENT_CERT ${lib.escapeShellArg baoDeploy.clientCertFile}"
"--set-default BAO_CLIENT_KEY ${lib.escapeShellArg baoDeploy.clientKeyFile}"
];
in
pkgs.symlinkJoin {
name = "bao-hive";
paths = [ baoDeploy.package ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/bao ${lib.concatStringsSep " " wrapArgs}
'';
};
extraListeners = lib.listToAttrs (
lib.imap1 (
i: addr:
@ -678,6 +711,12 @@ in
})
(lib.mkIf (hyperhiveCfg.enable && deployCfg.bao.enable) {
# The wrapper alone, never `baoDeploy.package`: openbao's own module
# installs the CLI *inside* the container, and this host had none at all,
# so an operator either hopped into the container or re-typed the
# addresses on every command.
environment.systemPackages = [ baoCli ];
# The in-container unit plus the two host-side ones this module defines.
# `swarm-bao-pki` and `swarm-bao-matrix-token` are declared by the glue
# modules that create them, per the option's own rule — and a name