swarm-controller: accept an agent's external matrix account and put it in the store
The swarm UI had nowhere to POST an external matrix account to: this daemon had no matrix-account code at all and no `swarm-secret-client` dependency, so the last leg of #3726 — a credential reaching an agent — had no entry point. `PUT /api/hives/{hive}/agents/{agent}/matrix-accounts/{account}` writes the credential to the store under the agent's own path and publishes a `CredentialNotice` on that hive's credential subject. All three path names are load-bearing: agent + account locate the secret, hive routes the notice. The account is a path segment rather than a body field so that splitting the 1:1 account-to-agent mapping later is a new route, not a changed payload. Store first, notify second, and the order cannot be swapped: a notice that overtakes its own write reaches a hive that reads nothing, and the hive deliberately does not retry. The publish is followed by a flush for the reason `publish_deploy` flushes — `publish` hands the message to the connection's write buffer and returns, so the response could otherwise outrun the notice it reports as sent. The store client is built per request rather than held in `AppState`, matching what the hive side does inside `deliver`: a login that expires is not worth caching for a route this cold. `swarm_hive` is `declaration_target`'s two name checks, extracted so this handler makes them identically rather than in a second copy free to drift. `declaration_target` still tests the writer first, so a deployment with no queue answers 503 whatever the caller spelled. ## The nix half #4081 minted the controller's leaf and gave it `baoClientCertFile` / `baoClientKeyFile`, deliberately stopping there — the leaf is minted whether or not a controller runs on that host. Nothing consumed those options, so the identity never reached the process. Measured before writing: `git grep baoClientCertFile` returned 5 sites and zero consumers, against a control (`tokenEndpoint`, 4 hits in the same file) proving the search can see consumption where it exists. The unit now gets `BAO_ADDR` / `BAO_CLIENT_CERT` / `BAO_CLIENT_KEY` / `BAO_CACERT` and the matching `LoadCredential` entries, following `hive-c0re/environment.nix`'s `%d` credential shape. The gate is `deploy.swarm-controller.baoClientCertFile`, NOT `deploy.bao.clientCertFile`. The latter is the hive reader's identity and its policy scopes a hive's own secrets; wiring it here would evaluate, deploy, and fail only when the daemon tried to write an agent's credential. Two `module-eval` arms cover exactly that. The presence arm asserts the `LoadCredential` *source path* (`…:/var/lib/swarm-bao-pki/controller.pem`) and not just the `%d` name, because a `%d`-only assertion passes while the daemon holds the wrong policy. The absence arm (`controllerNoStore`) is what makes the presence arm mean anything. `RestrictAddressFamilies` already covers the store client; its own comment asks for the family to be added with the client, and AF_INET/AF_INET6 are present. Contributes to #3726
This commit is contained in:
parent
e263681f1d
commit
0d88ca5e7f
6 changed files with 252 additions and 14 deletions
|
|
@ -18,6 +18,37 @@ let
|
|||
deployCfg = config.services.hyperhive.deploy;
|
||||
autheliaCfg = config.services.hyperhive.swarm.authelia;
|
||||
|
||||
# Where the secret store is, and whether this host holds the controller's
|
||||
# own leaf for it. ⚠️ The controller's pair, NOT `deploy.bao.clientCertFile`
|
||||
# — that one is the hive reader's, and its policy scopes a hive's own
|
||||
# secrets. The two are deliberately separate identities.
|
||||
#
|
||||
# Gated on the leaf, never on `deploy.bao.enable`: a controller three
|
||||
# networks away from the store holds a leaf issued out of band and wants
|
||||
# exactly this wiring. Same rule ./glue-controller-bao-identity.nix states
|
||||
# for the paths themselves.
|
||||
baoCfg = config.services.hyperhive.swarm.bao;
|
||||
haveBaoIdentity =
|
||||
deployCfg.swarm-controller.baoClientCertFile != null
|
||||
&& deployCfg.swarm-controller.baoClientKeyFile != null;
|
||||
|
||||
# `swarm_secret_client` reads these spellings explicitly rather than
|
||||
# vaultrs's `VAULT_*` defaults — falling through to those builds a client
|
||||
# with no identity and fails at the TLS handshake, naming neither. `%d` and
|
||||
# not the paths themselves: see the `LoadCredential` below.
|
||||
baoEnv =
|
||||
lib.optionalAttrs haveBaoIdentity {
|
||||
BAO_ADDR = "https://${baoCfg.domain}:${toString baoCfg.port}";
|
||||
BAO_CLIENT_CERT = "%d/bao-client.pem";
|
||||
BAO_CLIENT_KEY = "%d/bao-client-key.pem";
|
||||
}
|
||||
// lib.optionalAttrs (haveBaoIdentity && deployCfg.bao.serverCaFile != null) {
|
||||
# Absent means the system trust store — right for a real CA, wrong for
|
||||
# the self-signed one ./glue-bao-tls.nix mints, which is why that file
|
||||
# names this path rather than leaving it to a default.
|
||||
BAO_CACERT = "%d/bao-ca.pem";
|
||||
};
|
||||
|
||||
# What `swarmctl` needs in order to act on authelia from the host.
|
||||
#
|
||||
# Only set when authelia actually runs **here**: the controller can be
|
||||
|
|
@ -645,7 +676,17 @@ in
|
|||
]
|
||||
++ lib.optional (
|
||||
deployCfg.swarm-controller.forgeTokenFile != null
|
||||
) "forge-token:${deployCfg.swarm-controller.forgeTokenFile}";
|
||||
) "forge-token:${deployCfg.swarm-controller.forgeTokenFile}"
|
||||
# The store identity, same shape and same reason as hive-c0re's: the
|
||||
# key is root-owned `0600` and this daemon runs as `swarm-controller`,
|
||||
# so it never gets read access to the original file.
|
||||
++ lib.optionals haveBaoIdentity [
|
||||
"bao-client.pem:${deployCfg.swarm-controller.baoClientCertFile}"
|
||||
"bao-client-key.pem:${deployCfg.swarm-controller.baoClientKeyFile}"
|
||||
]
|
||||
++ lib.optional (
|
||||
haveBaoIdentity && deployCfg.bao.serverCaFile != null
|
||||
) "bao-ca.pem:${deployCfg.bao.serverCaFile}";
|
||||
|
||||
# The placeholder default that makes the above non-fatal.
|
||||
# `LoadCredential=` takes priority over `SetCredential=`, so this is
|
||||
|
|
@ -770,6 +811,7 @@ in
|
|||
// webhookEnv
|
||||
// authBridgeEnv
|
||||
// swarmNameEnv
|
||||
// baoEnv
|
||||
// otelEnv;
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue