Watch
0
0
Fork
You've already forked hyperhive
0

the operator's bao works on a store host without a hive

This commit is contained in:
atlas 2026-11-11 03:09:07 +01:00 • committed by mara
commit 32155f70aa
4 changed files with 169 additions and 9 deletions

View file

@ -99,7 +99,10 @@ and the root token **once**, and stores none of them: not in a file, and the roo
never in a process's argument list (so not in `ps`). Record them off this host before you do
anything else. It refuses an initialised store, and works
from any host that can reach the store and has `bao` on `PATH`; the
wrapper installed on the store host already knows the store's address and CA.
wrapper installed on the store host already knows the store's address and CA, and
presents a client certificate: the hive's own where a hive runs on that host, else
the `operator` leaf that `swarm-bao-pki` mints. Both open the TLS handshake, and
neither logs in to a role.
Then bootstrap the **granter**, the one principal that writes every
`swarm-*` policy and role from then on. This is the only step that needs

View file

@ -179,7 +179,13 @@ over the VPN (for a hive, `ca.pem` and the files from
`/var/lib/swarm-bao-pki/hives/<name>/`), then deploy the other machines. The
private keys travel over a channel you trust and stay readable by root only.
[`credentials.md`](credentials.md) lists which leaf each service reads and what
a missing one costs. The first-run steps after the deploys are in
a missing one costs.
On `swarm-central` itself, `bao status`, `bao operator init` and `bao operator
unseal` work as root with no extra step. `swarm-central` runs no hive, so the
`bao` wrapper presents the `operator` leaf that the store's host mints for
itself. That leaf logs in to no role, so every other call still needs a token.
The first-run steps after the deploys are in
[`setup.md`](../../1-getting-started/setup.md).
## DNS

View file

@ -125,6 +125,14 @@ store's host signs them and the operator copies them to the host that needs
them and names them in config. The service principals' leaves are at the top of
`/var/lib/swarm-bao-pki/`, named after the principal.
The host's `bao` wrapper presents the hive's own `client.pem` where a hive runs
on the host, and otherwise `operator.pem`, which `swarm-bao-pki` mints on every
store host (subject `deploy.bao.operatorCommonName`, `operator` by default; key
`0600` root). No cert-auth role accepts that subject, so it opens the TLS
handshake and logs in to nothing: `bao status`, `operator init` and `operator
unseal` need no token and work on a store host without a hive, and every other
call still needs a token. Don't copy it to another host.
For every hive in `swarm.hives` other than its own, the store's host signs
that hive's two leaves into `/var/lib/swarm-bao-pki/hives/<hive>/`:

View file

@ -132,6 +132,7 @@ pieces fit, and the reasoning behind them, is in [`credentials.md`](../../2-hand
| the swarm controller's webhook secret | `swarm-controller` stores it in the swarm secret store at `swarm/controller/swarm-controller/forge/webhook-secret` when the store holds none: the value of `webhook-secret` in its state directory, or a new one when there is no such file. The state directory file stays on disk and is the fallback while the store is unreachable or its policy doesn't grant the path yet | the swarm secret store; the controller holds it in memory and registers the same value with the forge's hooks | nothing to place: the controller's store leaf already exists. A store that stays unreachable on a start with no state directory file leaves the webhook endpoint answering 503 until the store answers: the controller re-reads it in the background, at most five minutes apart, and registers the forge hooks once it has the secret |
| the metrics store responder's own store leaf (cert + key) | `signLeaf` in `glue-bao-tls.nix` (`swarm-bao-pki.service`), on the host that runs the secret store, once, subject `deploy.bao.metricsAuthCommonName`. `swarm-bao-metrics-auth-policy.service` writes the `swarm-metrics-auth` role and its read grants on the bearer leaf and on the metrics store forwarder's client secret, nothing else | `/var/lib/swarm-bao-pki/metrics-auth.pem` and `metrics-auth-key.pem` on the store's host, handed to `swarm-metrics-auth` through `LoadCredential` (paths in argv, never values) and read by path by `swarm-victoriametrics-forwarder-oidc` | **the operator** places a leaf with that subject on the metrics store's host and names it in `deploy.victoriametrics.authBaoClientCertFile` / `authBaoClientKeyFile`; evaluation fails naming both until then. If the store is on another host, deploy that host first: the role must exist before the responder's login succeeds, and until it does the gateway refuses every Grafana query (fail closed) |
| the secret publisher's store leaf (cert + key) | `signLeaf` in `glue-bao-tls.nix` (`swarm-bao-pki.service`), on the host that runs the secret store, once, subject `deploy.bao.secretPublisherCommonName`. `swarm-bao-secret-publisher-policy.service` writes the `swarm-secret-publisher` role: create and update under `swarm/hives/*`, `swarm/services/*` and the controller's client, and read on the `oidc/client` leaves it mints, the controller's client and authelia's five keys alone | `/var/lib/swarm-bao-pki/secret-publisher.pem` and `secret-publisher-key.pem` on the host, handed into authelia's container as nspawn system credentials, which `authelia-swarm-secrets`, `swarm-authelia-bridge` and the forwarder's `swarm-journal-forwarder-oidc` load with `LoadCredential`. nspawn won't start authelia's container while a named file is missing | **the operator** copies the leaf to authelia's host, beside the hive's own store leaf, where `glue-secret-publisher-bao-identity.nix` looks, or names it in `deploy.swarm-secret-publisher.baoClientCertFile` / `baoClientKeyFile` |
| the operator's store leaf (cert + key) | `signLeaf` in `glue-bao-tls.nix` (`swarm-bao-pki.service`), on every host that runs the secret store, once, subject `deploy.bao.operatorCommonName`. No cert-auth role accepts that subject, so the leaf opens the TLS handshake and logs in to nothing | `/var/lib/swarm-bao-pki/operator.pem` and `operator-key.pem` on the host, `0600` root; the host's `bao` wrapper presents it when the host runs no hive, via `deploy.bao.operatorClientCertFile` / `operatorClientKeyFile`. A store host from before this leaf has no file until `swarm-bao-pki` next runs, and its wrapper sets no client certificate | nothing to place: the leaf is for the shell on the store's host and is never copied to another host |
| the swarm controller's copy of its OIDC secret | `swarm-controller` reads it out of the swarm secret store at start, under its own leaf | the daemon's memory only; never on disk | same read, co-located or not. Nothing for an operator to place beyond the controller's store leaf (`deploy.swarm-controller.baoClientCertFile`) |
| a hive's copy of its own OIDC secret | hive-c0re reads it out of the swarm secret store when it first connects to the queue, under the hive's own leaf | the daemon's memory only; never on disk | same read, co-located or not. Nothing for an operator to place beyond the hive's store leaf (`deploy.bao.clientCertFile`) |
| the hive collector's copy of its hive's OIDC secret | `swarm-bao-hive-otel-oidc.service` reads it out of the swarm secret store under the hive's own leaf, **on every hive that runs the collector and holds that leaf**; the leaf is the source | `/run/hive-otel-oidc/hive-<name>.secret` on the hive's host tmpfs, 0400 root, loaded by `LoadCredential` — never on persistent disk, so a reboot fetches it again | same unit, same path, co-located or not. Nothing for an operator to place beyond the hive's store leaf (`deploy.bao.clientCertFile`). Naming a secret file with `otel.clientSecretFile` instead is legacy for a host without a leaf: the build warns, and the fix is to place the leaf from `/var/lib/swarm-bao-pki/hives/<hive>/` and drop the setting |