Second split under docs/swarm/, following the shape docs/turn-loop/ and docs/web-ui/ already use. The README keeps the `## Swarm CA` heading rather than deleting it: an existing cross-reference targets that anchor, and a heading is the cheapest thing to preserve. It now carries the one-paragraph summary and a pointer, so a reader who lands on the anchor still arrives somewhere that answers the question. The CA page also absorbs three things that were true but undocumented — why the trust bundle rather than the bare intermediate (openssl will not terminate a chain at a non-self-signed anchor without -partial_chain), that autoConfigure derives from enableAllLocalDefaults, and that the name constraint excludes both IP families because a permitted-DNS-only constraint says nothing about IP SANs.
92 lines
4.2 KiB
Markdown
92 lines
4.2 KiB
Markdown
# Swarm CA
|
|
|
|
A hive's internal TLS chains to a **swarm root CA**: the root signs each
|
|
hive's own CA, and that hive CA signs the gateway leaf. A peer that
|
|
trusts the root once validates every hive in the swarm, present and
|
|
future, instead of being pinned to each one by hand.
|
|
|
|
That is the whole point of the hierarchy — it turns per-peer trust from
|
|
O(n²) hand-pinning into one anchor per swarm.
|
|
|
|
## Two provisioning modes, one structure
|
|
|
|
What differs is who puts the artifacts on disk:
|
|
|
|
| | swarm root | this hive's CA |
|
|
| --- | --- | --- |
|
|
| default | operator-provided | operator-provided, else self-signed as before |
|
|
| `autoConfigure = true` (all on one host) | generated by `swarm-ca.service` on first boot | issued by `hive-tls-ca.service` under the root |
|
|
|
|
`services.hyperhive.swarm.ca.autoConfigure` selects between them, and is
|
|
**off by default**: a swarm's services and its hives can live on
|
|
different hosts, and a host cannot tell whether it is the one holding
|
|
the root, so setting the swarm CA up is an operator action rather than
|
|
something a host assumes. Turn it on for an all-on-one-host deployment
|
|
and the hierarchy costs no configuration.
|
|
|
|
It defaults from `services.hyperhive.enableAllLocalDefaults`, the single
|
|
switch that says "this box is the whole deployment".
|
|
|
|
**A hive given neither artifact keeps the self-signed CA it has always
|
|
had.** It serves TLS exactly as before and simply isn't part of a
|
|
swarm's trust hierarchy — the right outcome for a hive nobody has
|
|
federated yet. Only `autoConfigure` issues a hive sub-CA, because only
|
|
that case can: signing one needs the root's private key.
|
|
|
|
Moving the swarm CA onto its own host is then a matter of moving
|
|
`services.hyperhive.swarm.ca.stateDir` and leaving `autoConfigure` off —
|
|
there is no second code path to switch to.
|
|
|
|
## Constraints on the material
|
|
|
|
The 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. Only certificates are
|
|
distributed.
|
|
|
|
Each hive CA is **name-constrained** (X.509 `nameConstraints`) to that
|
|
hive's own domain, so a hive CA that leaks can only mint names inside
|
|
its own subdomain — enforced by every verifier rather than by
|
|
convention. The constraint excludes both IP families as well, since a
|
|
permitted-DNS-only constraint says nothing about IP SANs.
|
|
|
|
The root is issued with `pathlen:1`: it may sign hive CAs, and those may
|
|
sign leaves, and the chain stops there.
|
|
|
|
## What to hand a peer
|
|
|
|
`hivectl peer-config` prints the `cp` line. The file is
|
|
`<tls.stateDir>/trust-bundle.pem` — the hive CA plus the swarm root —
|
|
**not `ca.pem`**.
|
|
|
|
The distinction is load-bearing rather than cosmetic: once a hive CA is
|
|
an intermediate, it is no longer something a verifier can build a chain
|
|
*to*. OpenSSL will not terminate a chain at a trusted non-self-signed
|
|
certificate without `-partial_chain`, so handing a peer the bare
|
|
intermediate produces a verification failure that reads like a bad cert
|
|
rather than like a missing anchor. The bundle carries both, so the same
|
|
file works whichever mode issued it.
|
|
|
|
On a hive that predates the swarm root the bundle is just that hive's
|
|
self-signed CA, so the recipe does not change.
|
|
|
|
⚠️ Consumers must read `trust-bundle.pem`, never `ca.pem` directly. A
|
|
consumer that reads `ca.pem` works fine on a hive that has always been
|
|
self-signed and breaks the moment that hive adopts the hierarchy — so
|
|
the failure is invisible on the deployment you are most likely to test
|
|
on.
|
|
|
|
## Adopting the hierarchy on an existing hive
|
|
|
|
An existing `ca.pem` is never re-rooted automatically — swapping it
|
|
would break every consumer that already trusts it, and agents only pick
|
|
up new trust when their container restarts. To adopt, delete `ca.pem` +
|
|
`ca-key.pem` under `tls.stateDir` and restart `hive-tls-ca.service`;
|
|
the CA is re-issued under the root and the leaf re-signed. Until then
|
|
the hive serves TLS exactly as before and is simply not part of the
|
|
swarm's trust hierarchy.
|
|
|
|
Making that adoption a first-class, non-disruptive operation — rather
|
|
than a documented `rm` — is tracked separately; the mechanism it needs
|
|
(carrying the previous CA in the trust bundle across the overlap)
|
|
already exists.
|