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

@ -29,6 +29,16 @@ let
# The hive leaf exists only where a hive runs; ../hive-tls.nix issues it
# under that same condition.
runsHive = config.services.hyperhive.deploy.hive-controller.enable;
# Whether the cert import `Requires=` the services leaf's issuing unit
# (../hive-tls.nix decides). Where it does, a missing leaf fails the
# import rather than anything standing in for it.
svcLeafRequired = lib.elem "hive-gateway-self-signed-cert.service" (
config.systemd.services.swarm-services-cert.requiredBy or [ ]
);
svcLeafMissing = ''
echo "no swarm-services leaf at ${tlsStateDir}/swarm-services.pem, and this gateway serves swarm names only with it." >&2
echo "swarm-services-cert.service issues it from the secret store's pki mount; its journal says why it has not." >&2
exit 1'';
# Every vhost claiming `default_server`, ours and the operator's
# alike. Computed once so the assertion below and the message it
@ -252,10 +262,9 @@ in
# 🚨 DO NOT "simplify" this into pointing nginx at the CA dir. It does
# TWO jobs: it re-modes the leaf to the 0640 root:nginx copy below (a
# 0600 key fails nginx's pre-start config test and blocks the unit),
# and it guarantees every cert path the config names exists — which is
# what the swarm-services fallback at the bottom of the script is for.
# A leaf that never issues therefore takes the WHOLE gateway down, not
# one vhost. Both mechanisms, and what it cost the time this was
# and it puts a file at every cert path the config names or fails: one
# nginx cannot find takes the WHOLE gateway down, not one vhost. Both
# mechanisms, and what it cost the time this was
# collapsed: `docs/networking/gateway.md::Self-signed TLS (default)`.
#
# ALWAYS runs (no ConditionPathExists) and is idempotent, because it
@ -307,49 +316,51 @@ in
# because the hive leaf's role allows only its own hive's
# domain, and the service names are siblings of it.
#
# Absent is a normal state, not a failure: a host fronting
# no swarm name has none, and issuance can also fail on a
# host that wants one.
#
# ⚠️ When it is absent the HIVE leaf goes to this path
# anyway, and that fallback is load-bearing rather than
# tidy. nginx refuses to load a config naming a cert file
# that does not exist — `cannot load certificate … no such
# file` fails the pre-start test, so the vhost does not
# degrade, the ENTIRE gateway dies and takes the forge, the
# dashboard and matrix with it. Serving the hive leaf on a
# swarm-service name is a name mismatch: browsers warn,
# strict clients refuse, everything else keeps working, and
# the operator gets a bad cert instead of no hive.
#
# Measured, not theorised: this exact path took pr1ma's
# gateway down when the services sub-CA failed to issue.
# Where the gateway waits for it, a missing leaf fails this
# unit and nginx with it. Elsewhere — no swarm name here, or
# only the store's UI, whose host must start its passthrough
# with the store sealed — the hive leaf stands in, because
# nginx refuses a config naming a missing cert file, until
# `swarm-services-cert` issues one and re-runs this unit.
if [ -s ${tlsStateDir}/swarm-services.pem ]; then
install -m 0644 ${tlsStateDir}/swarm-services.pem ${svcCert}
install -m 0640 -g nginx ${tlsStateDir}/swarm-services-key.pem ${svcKey}
else
echo "no swarm-services leaf — serving the hive leaf on those names (mismatch, not an outage)" >&2
install -m 0644 ${tlsStateDir}/gateway.pem ${svcCert}
install -m 0640 -g nginx ${tlsStateDir}/gateway-key.pem ${svcKey}
${
if svcLeafRequired then
svcLeafMissing
else
''
echo "no swarm-services leaf yet — serving the hive leaf on those names until swarm-services-cert.service issues one" >&2
install -m 0644 ${tlsStateDir}/gateway.pem ${svcCert}
install -m 0640 -g nginx ${tlsStateDir}/gateway-key.pem ${svcKey}''
}
fi''
else
''
# No hive here, so no hive leaf: every vhost, the catch-all
# included, serves the swarm-services leaf. Until it lands a
# throwaway self-signed pair stands in, because nginx refuses
# a config naming a missing cert file. A store-only host does
# not wait for the leaf, so its stream passthrough comes up with
# the store sealed. `swarm-services-cert` re-runs this unit
# once the leaf lands.
# included, serves the swarm-services leaf. Where the gateway
# waits for it, a missing leaf fails this unit. A store-only
# host does not wait, so its stream passthrough comes up with
# the store sealed: a throwaway self-signed pair stands in,
# because nginx refuses a config naming a missing cert file,
# until `swarm-services-cert` issues the leaf and re-runs this
# unit.
if [ -s ${tlsStateDir}/swarm-services.pem ] && [ -s ${tlsStateDir}/swarm-services-key.pem ]; then
cert=${tlsStateDir}/swarm-services.pem
key=${tlsStateDir}/swarm-services-key.pem
else
echo "no swarm-services leaf yet — serving a throwaway self-signed certificate until it lands" >&2
cert=${tlsDir}/fallback.pem
key=${tlsDir}/fallback-key.pem
(umask 077; openssl req -x509 -newkey rsa:2048 -nodes -sha256 -days 30 \
-subj "/CN=hyperhive-gateway-fallback" -keyout "$key" -out "$cert")
${
if svcLeafRequired then
svcLeafMissing
else
''
echo "no swarm-services leaf yet — serving a throwaway self-signed certificate until it lands" >&2
cert=${tlsDir}/fallback.pem
key=${tlsDir}/fallback-key.pem
(umask 077; openssl req -x509 -newkey rsa:2048 -nodes -sha256 -days 30 \
-subj "/CN=hyperhive-gateway-fallback" -keyout "$key" -out "$cert")''
}
fi
install -m 0644 "$cert" ${tlsCert}
install -m 0640 -g nginx "$key" ${tlsKey}