Watch
0
0
Fork
You've already forked hyperhive
0

swarm CA: retire the host-held root CA, the store holds the only anchor

The secret store's pki root issues every hive's gateway leaf and the
swarm's service names, so the root swarm-ca.service generated on the
host anchors nothing and its key is a secret outside the store.

- swarm-ca.nix is gone: swarm-ca.service, root.pem and root-key.pem,
  and the swarm.ca.{autoConfigure,stateDir,validityDays} options. A
  definition of any of them fails evaluation with a removal message
  (swarm-ca-removed.nix), and a tmpfiles rule deletes /var/lib/swarm-ca
  on every host. singleHostSwarm no longer sets a swarm.* option.
- hive-tls-ca drops the $root slot: trust-bundle.pem holds the store's
  pki root and a hive CA's public ca.pem/ca-previous.pem only.
- hivectl peer-config no longer prints the scp line for the swarm root;
  the store's pki root is the one step.
- The gateway's cert import no longer stands a hive leaf or a throwaway
  in for a missing swarm-services leaf where it Requires= the issuing
  unit: it fails, naming swarm-services-cert.service. The stand-in stays
  where the gateway does not wait (no swarm name, or the store's UI
  only, whose passthrough must start with the store sealed).

Comments and docs that named swarm-ca.nix, swarm.ca.* or the host root
now name the store's pki root.

Refs #4588
This commit is contained in:
atlas 2026-10-05 19:37:35 +02:00 • committed by mara
commit 93f1a76ea7
22 changed files with 305 additions and 395 deletions

View file

@ -83,8 +83,9 @@ the file every consumer anchors on. It holds whichever of these exist:
issues under that CA; they stay in the bundle so a leaf it signed verifies
until `hive-tls-resign` replaces it. Removing them is a deliberate later
step.
- the swarm root, `<swarm.ca.stateDir>/root.pem`, on the host that generates
it.
No other anchor goes in: the store's `pki` root is the only root the swarm
has.
No hive generates a CA or signs with a CA key. A
`<deploy.hive-controller.tls.stateDir>/ca-key.pem` stays on disk, unread:
@ -163,46 +164,34 @@ role's `allowed_domains`, enforced by the store at issue time: a hive's
identity that leaks can get certificates for that hive's names and no
others.
The swarm root's private key never reaches the nix store: the store is
world-readable and content-addressed, so a key committed to a flake is a
key published to everyone who builds it. The swarm distributes only
certificates.
No hive holds a root CA or its key, and nothing under `/var/lib/swarm-ca`
exists: every host deletes that directory at boot and on every deploy. The
swarm distributes only certificates.
## What to hand a peer
`hivectl peer-config` prints the `services.hyperhive.swarm.hives."<name>"`
block a peer operator pastes into their own config. When this hive's
gateway serves a self-signed leaf (detected by the presence of
`<deploy.hive-controller.tls.stateDir>/trust-bundle.pem`), it also prints a
one-time `scp` line installing the **swarm root** —
`<swarm.ca.stateDir>/root.pem`, not this hive's own CA — on the peer's
host:
```
scp /var/lib/swarm-ca/root.pem <peer-host>:/var/lib/swarm-ca/root.pem
```
It then prints the secret store's `pki` root itself, read from
`<deploy.hive-controller.tls.stateDir>/trust-bundle.pem`), it also prints the
secret store's `pki` root itself, read from
`/var/lib/hive-tls/swarm-services-root.pem`. The store issues every hive's
gateway leaf and the swarm's service names under that root, so a peer host
trusting only the swarm root can't verify them.
gateway leaf and the swarm's service names under that root, so it's the one
anchor a peer host needs.
The operator installs the roots **once per swarm host**, not once per peer,
The operator installs the root **once per swarm host**, not once per peer,
so a hive joining later needs no edit on the hives already running. A hive
whose cert already chains to a public CA has nothing to install —
`peer-config` says so instead.
## Distributing the root
The root **key** is a runtime file for the reason in
[Constraints on the material](#constraints-on-the-material).
The root **certificate** is a runtime file as a _consequence_ — it lives
beside the key under `swarm.ca.stateDir` — and that has a cost worth
The root **certificate** is a runtime file: each hive fetches it from the
store at boot, so nothing can name it at build time. That has a cost worth
naming, because it's subtle and it bites at a distance:
> **Nothing whose trust store nix assembles at build time can reference
> the swarm root.** The derivation reads `security.pki.certificateFiles`
> the store's root.** The derivation reads `security.pki.certificateFiles`
> inside itself; the root doesn't exist there.
Two consumers, and only one of them is fine:
@ -231,7 +220,7 @@ Two consumers, and only one of them is fine:
> fixed. The same caveat applies to `hive-forge` (Go) for the same
> reason.
To put the root on another host, copy the certificate to the same path
there (`scp <stateDir>/root.pem <host>:<stateDir>/root.pem`). One anchor
per host, not one per peer: a hive joining later needs no edit on the
hives already running, which is the whole point of the hierarchy.
A hive in the swarm fetches the root itself. A host outside it gets the
certificate from `hivectl peer-config` ([What to hand a peer](#what-to-hand-a-peer)).
One anchor per host, not one per peer: a hive joining later needs no edit
on the hives already running.