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

@ -597,17 +597,15 @@ let
#
# The mount's own root is generated into it by the bootstrap unit below and
# its private key never leaves — `root/generate/internal` keeps it inside
# the store, which is the whole reason the mount exists rather than another
# key beside ./swarm-ca.nix's.
# the store.
#
# An option rather than a literal because ./hive-tls.nix issues through it
# and has to spell it the same way.
servicesPkiMountPath = baoDeploy.servicesPkiMountPath;
# Subject of the root generated into that mount. A label for a human reading
# a chain, not an identity anything authenticates against — same fall-through
# ./swarm-ca.nix:29-37 uses, and for the same reason: a hive that has set
# neither a swarm name nor a domain must still evaluate.
# a chain, not an identity anything authenticates against. Falls through so
# a hive that has set neither a swarm name nor a domain still evaluates.
servicesPkiRootLabel =
if hyperhiveCfg.swarm.name != null then
hyperhiveCfg.swarm.name
@ -624,11 +622,9 @@ let
servicesPkiRootDir = "/var/lib/swarm-bao-services-pki";
servicesPkiRootFile = "${servicesPkiRootDir}/services-root.pem";
# ~30y, matching ./swarm-ca.nix's root rather than its services sub-CA. This
# is an ANCHOR: an operator adds it to a browser's trust store by hand, and
# every rotation is a manual re-trust on every machine that holds it. The
# sub-CA's short window bought cheap rotation because the root above it
# absorbed the churn; nothing sits above this one.
# ~30y. This is an ANCHOR: an operator adds it to a browser's trust store by
# hand, and every rotation is a manual re-trust on every machine that holds
# it; nothing sits above this one to absorb the churn.
servicesPkiRootTtl = "262800h";
# The leaf's window, pinned on the role rather than left to the mount's
@ -645,16 +641,16 @@ let
# The role every swarm-service certificate will be issued through, and the
# whole narrowing of this mount. A role rather than a name-constrained
# sub-CA: the engine enforces the same "these names and no others" at issue
# time that ./swarm-ca.nix builds into x509 `nameConstraints` today, so a
# second CA in the chain would re-implement a check that already happens.
# sub-CA: the engine enforces "these names and no others" at issue time, so
# x509 `nameConstraints` on a second CA in the chain would re-implement a
# check that already happens.
servicesPkiRoleName = baoDeploy.servicesPkiRoleName;
# The names that role may issue for. Read out of the swarm-tier option
# rather than assembled here, exactly as ./swarm-ca.nix:45 and
# ./hive-tls.nix:16 read it — the thing that narrows what may be issued and
# the leaf that carries those names as SANs must agree, and a second module
# composing its own list is how they stop agreeing.
# rather than assembled here, exactly as ./hive-tls.nix reads it — the thing
# that narrows what may be issued and the leaf that carries those names as
# SANs must agree, and a second module composing its own list is how they
# stop agreeing.
swarmServiceDomains = hyperhiveCfg.swarm.serviceDomains;
# The identity that asks this mount for a certificate. A FOURTH principal
@ -1374,7 +1370,7 @@ in
clientCaFile = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
example = "/var/lib/swarm-ca/root.pem";
example = "/var/lib/swarm-bao-pki/ca.pem";
description = ''
Authority the store validates hive **client** certificates against.
@ -1385,7 +1381,7 @@ in
On a hive that runs the store, a glue module supplies the CA it
minted for exactly this, as a `mkDefault`. Point this at something
else — the swarm root, an operator's own CA — and yours wins.
else — an operator's own CA — and yours wins.
`null` leaves client-certificate verification off, which is only
appropriate where something else authenticates the connection.
@ -2806,21 +2802,18 @@ in
#
# Every narrowing is load-bearing, and three of them turn OFF a
# default that is on:
# * `allow_subdomains` — the x509 constraint ./swarm-ca.nix writes
# permits a whole subtree per name; a swarm service is an exact
# hostname, so the role is the narrower of the two.
# * `allow_subdomains` — a swarm service is an exact hostname, not
# a subtree.
# * `allow_localhost` — bao's own default is `true`, which would
# make this role issue for a name nobody configured.
# * `allow_ip_sans` — the sub-CA excludes both IP subtrees
# explicitly (./swarm-ca.nix:260-276); an unconstrained name type
# is a name type this issuer is unconstrained for.
# * `allow_ip_sans` — an unconstrained name type is a name type
# this issuer is unconstrained for.
# `server_flag` alone because that is what today's leaf carries
# (`extendedKeyUsage=serverAuth`, ./hive-tls.nix:144), and rsa:4096
# because that is the key the current implementation generates.
#
# An empty `swarm.serviceDomains` renders an empty `allowed_domains`,
# which with `allow_any_name=false` is a role that issues nothing —
# the same outcome as ./swarm-ca.nix's "nothing to issue for" exit,
# which with `allow_any_name=false` is a role that issues nothing,
# reached without a branch.
bao write ${lib.escapeShellArg "${servicesPkiMountPath}/roles/${servicesPkiRoleName}"} \
allowed_domains=${lib.escapeShellArg (lib.concatStringsSep "," swarmServiceDomains)} \