diff --git a/docs/setup.md b/docs/setup.md index 4af6a569..da3d3d44 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -44,7 +44,41 @@ echo "hunter2" | hivectl gateway create-user mara --password-stdin hivectl gateway list-users ``` -### 3 · Swarm SSO (only when `deploy.authelia`) +### 3 · Secret store (only when `deploy.bao`) + +⚠️ **A sealed store still answers.** OpenBao starts uninitialised and +sealed, so the container is up and the port responds while every read +times out — the failure looks like a hang, not like a store that was +never initialised. Do this before anything is pointed at it. + +```bash +# On the host that RUNS the store, once. +bao operator init # keep the keys it prints and the root token OFF this host +``` + +Whether anything more is needed depends on +`services.hyperhive.deploy.bao.seal`: + +- **`pkcs11`** (the default) — the key is bound to the host's TPM and the + store unseals itself on every restart. `init` is the only manual step. +- **`shamir`** — no TPM, so `bao operator unseal` is needed again after + every restart, with the keys `init` printed. + +The store serves TLS, and on a hive that deploys it you need do nothing: a +first-boot unit mints a CA of the store's own plus the two leaves it signs — +the store's server certificate and this host's client certificate — and points +`deploy.bao.serverCertFile`, `.serverKeyFile` and `.clientCaFile` at them. + +Those are `mkDefault`s, so naming your own paths wins. Do that when your +certificates come from a real internal CA; the store has no opinion about +which. ⚠️ Not the gateway's HTTPS certificates and not the hive CA — this is +**mTLS between services and the store**, a separate trust domain, because a +store that took its identity from an authority it will itself distribute could +never come up before that authority. + +Making even the `init` unnecessary is tracked in issue #3768. + +### 4 · Swarm SSO (only when `deploy.authelia`) ⚠️ **Required to finish the install, not optional.** Authelia treats an empty user store as a fatal startup error, so until this runs the @@ -70,7 +104,7 @@ than amend — adding the group afterwards is `swarmctl user update mara Detail, including what the password is and why this stays manual: [`swarm/sso.md`](swarm/sso.md). -### 4 · Swarm UI (only when `deploy.swarm-ui`, on by default with the controller) +### 5 · Swarm UI (only when `deploy.swarm-ui`, on by default with the controller) Nothing to run — it is served on the swarm apex (`https:///`) as soon as the host rebuilds. Two things @@ -86,7 +120,7 @@ decide whether you can actually open it: Detail, including why reachability is deliberately not the access control: [`swarm/ui.md`](swarm/ui.md). -### 5 · Matrix +### 6 · Matrix ```bash # 5a. Ensure the hive-internal admin account exists first @@ -107,7 +141,7 @@ hivectl matrix promote-user mara The human operator's own matrix account is created via swarm SSO instead of a manual `hivectl` step — see step 3 (`swarmctl user add`). -### 6 · Spawn sub-agents +### 7 · Spawn sub-agents Sub-agent creation goes through the approval queue — ruth proposes, the operator approves, the container builds. From ruth's own turn (inside @@ -128,7 +162,7 @@ request_init_config(name: "iris") See [`approvals.md`](approvals.md) for the full flow. -### 7 · Useful host commands +### 8 · Useful host commands ```bash # Roster: all agents, status, rev, parent, pending reminders diff --git a/docs/swarm/secrets.md b/docs/swarm/secrets.md index a44b52ac..d43012f9 100644 --- a/docs/swarm/secrets.md +++ b/docs/swarm/secrets.md @@ -55,6 +55,8 @@ neither is a renaming of the other. | authelia subject store | `swarmctl` and `swarm-authelia-bridge` | `users.yml` — one file, read and written by both | `swarmctl`, on the host that runs authelia | | wireguard private key | **the operator** — `wg genkey` | whatever `swarm.wireguard.privateKeyFile` names | always operator-provided; nothing generates this for you | | queue auth-callout nkeys (user seed + account seed) | `swarm-nats-callout-keys` first-boot unit, when `nats.autoGenerateCallout` is set | `/var/lib/swarm-nats-callout/{callout-user,issuer}.seed`, `0600` | operator mints both with `nk` and names them in `nats.calloutUserSeedFile` / `nats.calloutIssuerSeedFile` | +| the secret store's own contents | openbao, on first `bao operator init` — **an operator action, not a unit** | `/var/lib/swarm-bao` on the host of whoever runs the store, bind-mounted into the `swarm-bao` container | n/a — there is one store; a hive elsewhere is a *client* of it and holds none of this | +| the secret store's unseal material | the HSM/TPM under `deploy.bao.seal = "pkcs11"`; openbao itself under `"shamir"` | in the token; or held by whoever ran `bao operator init`, which is what `"shamir"` means and why it is stated rather than inferred | n/a — only the host running the store seals anything | The three keys authelia mints for itself are generated in-container precisely because nothing outside that container ever reads them. **That is the test worth @@ -132,6 +134,29 @@ bounded wait, 120s — and then **fail loudly** rather than skipping. A silent s produces a service whose login button always fails, which is a symptom several layers from its cause. +The store's **first reader** is the matrix registration token, and it is worth +saying why that one: it is an opaque 32-byte value with no second file and no +format. Authelia's OIDC secret needs a `.secret` *and* a matching `.digest`, so +starting there would have meant debugging "can a reader authenticate and get +bytes back" and "did we write authelia's file format right" at once, with an +SSO outage as the failure mode. + +`glue-matrix-bao-token.nix` fetches it and writes the file `hive-matrix.nix` +already reads, so the homeserver never learns the store exists. Every failure +path — no such key, sealed store, unreachable store, empty value — leaves the +locally minted token in place, so a hive with no store behaves exactly as it +did before. + +⚠️ **Service↔store mTLS is its own trust domain.** A credential you must +already hold to authenticate cannot be fetched from the thing it authenticates +you to, so the store's identity cannot come from an authority the store +distributes — which excludes the hive CA and the swarm CA both, and has nothing +to do with the gateway's HTTPS certificates either way. `glue-bao-tls.nix` +mints a CA that signs exactly two things, the store's server certificate and a +reader's client certificate, and distributes nothing. A deployment with a real +internal CA deletes that file and names its own paths in +`deploy.bao.serverCertFile` / `clientCaFile`; the store itself has no opinion. + ## The constraint that decides where the root lives A hive CA carries `nameConstraints=permitted;DNS:`, and **a swarm