hive-c0re: hand the daemon the store identity it cannot open itself

`credential.rs` calls `SecretStore::from_env`, and nothing set `BAO_*` for
this unit — only `swarm-bao-matrix-token` had them. Fixing that alone would
not have been enough: hive-c0re runs as hive-core, and glue-bao-tls mints the
client key `0600` inside a `0700` directory, so the daemon cannot read the
file even when it is named.

Both halves go through LoadCredential, which this unit already uses for the
swarm-status client secret: root reads the identity at unit start, hive-core
sees it under `%d`, and no second on-disk copy exists.

The gate is the identity, never `deploy.bao.enable` — a hive that reads a
store on another machine holds a certificate and runs no store. Four
module-eval cases: the co-located and off-host presence arms, the absence arm
for a hive with no identity, and a presence/absence pair for the optional CA.
This commit is contained in:
atlas 2026-09-02 23:51:00 +02:00 committed by mara
commit ad439843f1
3 changed files with 103 additions and 1 deletions

View file

@ -13,6 +13,13 @@
let
cfg = config.services.hyperhive.c0re;
baoDeploy = config.services.hyperhive.deploy.bao;
# Held in one place because the LoadCredential below and the `BAO_*`
# environment in ./environment.nix have to agree on when they exist: a
# credential with no reader is dead weight, and an environment naming a
# credential nobody loaded is a daemon that fails at the TLS handshake.
baoClientIdentity = baoDeploy.clientCertFile != null && baoDeploy.clientKeyFile != null;
caTrust = import ../lib/hive-ca-trust.nix {
inherit lib;
tlsCfg = config.services.hyperhive.deploy.hive-controller.tls;
@ -309,7 +316,23 @@ in
# delivers into a container, across a filesystem boundary.)
lib.optional (
config.services.hyperhive.swarm.statusPublish.clientSecretFile != null
) "swarm-status-client.secret:${config.services.hyperhive.swarm.statusPublish.clientSecretFile}";
) "swarm-status-client.secret:${config.services.hyperhive.swarm.statusPublish.clientSecretFile}"
# The secret store's client identity, on the same reasoning one
# paragraph up — with a sharper edge: ./glue-bao-tls.nix mints the
# key `0600` inside a `0700` directory, so hive-core cannot read it
# at all. Loading it as a credential is what makes the store
# reachable from an unprivileged daemon without widening either.
# The gate is the identity, never `deploy.bao.enable`: a hive that
# reads a store on another machine holds one of these and runs no
# store. ./glue-matrix-bao-token.nix gates its own reader the same
# way.
++ lib.optionals baoClientIdentity [
"bao-client.pem:${baoDeploy.clientCertFile}"
"bao-client-key.pem:${baoDeploy.clientKeyFile}"
]
++ lib.optional (
baoClientIdentity && baoDeploy.serverCaFile != null
) "bao-ca.pem:${baoDeploy.serverCaFile}";
# Sandboxing. hive-c0re is unprivileged (runs as hive-core, never
# setuid), makes HTTP requests to forge/matrix/Anthropic (keeps INET),
# and delegates all privileged ops to hive-priv via a Unix socket.