Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/swarm-secret-client/README.md
atlas 48067d07e8 matrix: drop the per-hive appservice and its unused token
Every hive's homeserver loaded a per-hive `hyperhive` appservice
registration, and the swarm minted and delivered an as_token for it
through bao. No client presented that token: swarm-controller creates
every `@hive-<hive>` sender and every agent account with the swarm
appservice token (hive_sender.rs, agent_token.rs), and no Rust code read
the per-hive one.

Removed, with everything that existed only to serve it:
- hive-matrix.nix: the `hyperhive` registration, its activation-time
  render, the host bind mount of /var/lib/hyperhive/matrix-appservice,
  its LoadCredential entry, and the internal options
  `deploy.matrix.appserviceTokenFile` / `appserviceRegistrationScript`.
  tuwunel now loads only the swarm registration.
- glue-matrix-bao-token.nix and its `swarm-bao-matrix-token` unit.
- glue-bao-tls.nix: the matrix-token leaf and its defaults.
- swarm-bao.nix: the `swarm-matrix-token-<hive>` roles and policies
  (`swarm-bao-matrix-token-policy`), and the options
  `deploy.bao.matrixTokenClient{Cert,Key}File` and
  `matrixTokenCommonNamePrefix`, now mkRemovedOptionModule.
- swarm-secret-publisher.nix: the per-hive token mint and its state dir.
- swarm.nix: the reserved `<matrixTokenCommonNamePrefix>-<hive>` CN.
- swarm-secret-client: `matrix::appservice_token_path`.

The swarm registration (swarm-matrix-ctl, swarm-controller) is untouched.
Comments and docs that pointed at the removed glue file now point at
glue-queue-agent-credential.nix, which has the same reader shape.

Refs #4428
2026-10-05 07:48:17 +02:00

88 lines
4.4 KiB
Markdown

# swarm-secret-client
Reading and writing a swarm credential in the secret store, over `vaultrs`.
The HTTP is that crate's job. What this one owns is the **agreements** both
ends of the store have to state identically: where a credential lives, which
field its bytes are in, and how this deployment's environment becomes a
logged-in client.
The surrounding picture — which secret is minted where, and why delivery is a
copy rather than a bind mount — is [`docs/swarm/secrets.md`](../docs/swarm/secrets.md).
## Why a crate and not a module per binary
The store has two Rust ends and they are peers: the swarm controller writes a
credential, a hive reads it. Neither is senior to the other, so a path
formatted at each call site is an agreement with no owner — it holds right up
until one side is edited alone, and then it fails as a missing key rather than
as a mismatch.
The third end is what settles it. `nix/agent-modules/forge-token.nix` reads
the store with `bao kv get -field=value`. That reader is a shell line in
a nix module: it cannot be renamed by the same refactor as a Rust struct, and
no Rust test reaches it. So the field name is pinned by a test against the
serialised literal rather than left to the struct definition.
## The identity is a certificate, and the role is the hive's name
Authentication is the store's `cert` auth method.
`nix/host-modules/glue-bao-tls.nix` mints the client certificate with its CN
set to the hive's name, because a cert-auth role matches on the CN. So
`cert_role` is not a free choice for the caller: a hive passes its own name,
and the policy attached to that role is what scopes what it may read.
The listener's `tls_require_and_verify_client_cert` is a different thing and
not a substitute. It decides who may open a connection; it says nothing about
who the connection belongs to, and a store with the option set and no `cert`
mount configured refuses every login made here. That refusal is what
`Error::Vault` out of `connect` means.
## Configuration
`Settings::from_env` reads `BAO_ADDR`, `BAO_CLIENT_CERT`, `BAO_CLIENT_KEY`,
and the optional `BAO_CACERT`.
**The `BAO_` spellings are read explicitly rather than left to `vaultrs`.** Its
own defaults look for `VAULT_ADDR` / `VAULT_CLIENT_CERT` / `VAULT_CLIENT_KEY`,
which no unit in this tree sets. Falling through to them builds a client with
**no identity at all**, and that surfaces as a TLS handshake failure — a place
that names neither the variable nor the reason.
**Empty is as absent as unset.** systemd renders an unset nix option as
`Environment=BAO_CACERT=`, so empty is the shape a missing value arrives in.
**The certificate and key are paths, not values**, and are read at connect
time — same rule as every other credential in this tree, for the same reason:
a value in a nix expression is rendered into the world-readable store.
Reading the environment is separate from connecting (`Settings::from_lookup`)
because every one of those failures is a misconfiguration an operator has to
read an error about, and none of them needs a reachable store to happen.
## Names that arrive from elsewhere
`matrix::account_path` is fallible, which for a string formatter needs saying:
its segments are an agent name from the topology and an account name from that
agent's own config. A `/` turns one agent's segment into another agent's
directory and `..` walks out of the prefix entirely, so the charset it accepts
is deliberately narrower than what the store would.
## One module per kind of secret
`client` moves whatever type a caller names; it decodes nothing itself. What a
stored object _holds_ is stated in the module that also builds its path —
`matrix` today, and a second kind of swarm secret gets a module beside it.
The split is deliberate. A single shared struct that grows one field per
consumer ends up carrying, on every path, a field only one path's reader has
ever heard of; and the two things a kind of secret must pin — where it lives
and what is in it — are one agreement that reads worse split across modules.
## What this crate does not do
It has no opinion on **what** a caller may read. That is the policy attached to
the cert role, and it lives in the store.
It holds the token minted at login and renews nothing. A handle is built per
credential, so the login is the cheap part of a rare operation — a caller that
wanted to keep one alive across a token's lifetime would need more than this.