feat(tls): adopt the swarm CA automatically, but only where it is safe
A hive whose CA predates the swarm root keeps a self-signed CA, and nothing re-roots it. That is what makes the hierarchy non-disruptive, and it also means the O(1)-trust payoff never arrives for that hive. Adoption now happens by itself where this host owns the root, and nowhere else. The split is the whole design: adoption invalidates an anchor consumers already trust, and they refresh on their own schedule — on one box that schedule is knowable, across hosts it is not. So the all-local case migrates itself and every other case gets a failure that names both files, gives the two-command recipe, says why it is not automatic, and offers the marker as a deliberate opt-out. The previous CA rides in the trust bundle afterwards. Consumers read the bundle rather than ca.pem, so adoption is additive before it is subtractive; agents pick up new trust only when their container restarts, which is a window even on a single host. Dropping the old anchor stays a separate, deliberate step.
This commit is contained in:
parent
8e690c0694
commit
0befa4f36d
1 changed files with 60 additions and 5 deletions
|
|
@ -248,6 +248,49 @@ in
|
||||||
root=${lib.escapeShellArg "${swarmCaCfg.stateDir}/root.pem"}
|
root=${lib.escapeShellArg "${swarmCaCfg.stateDir}/root.pem"}
|
||||||
rootk=${lib.escapeShellArg "${swarmCaCfg.stateDir}/root-key.pem"}
|
rootk=${lib.escapeShellArg "${swarmCaCfg.stateDir}/root-key.pem"}
|
||||||
|
|
||||||
|
prev="$d/ca-previous.pem"
|
||||||
|
marker="$d/.swarm-ca-adopted"
|
||||||
|
|
||||||
|
# --- Adoption: a hive whose CA predates the swarm root.
|
||||||
|
#
|
||||||
|
# Runs ONLY where this host also owns the root (`autoConfigure`),
|
||||||
|
# because adoption invalidates an anchor that consumers already
|
||||||
|
# trust and they refresh on their own schedule. On one box that
|
||||||
|
# schedule is knowable; across hosts it is not, so there the
|
||||||
|
# operator does it and this unit only says so, loudly.
|
||||||
|
#
|
||||||
|
# Guarded by a marker rather than by the state of the world: the
|
||||||
|
# marker's ABSENCE is the trigger, so this fires once per hive
|
||||||
|
# instead of re-deciding every activation. That is the difference
|
||||||
|
# between a migration and a kicking machine.
|
||||||
|
if [ -s "$root" ] && [ -s "$ca" ] && [ ! -e "$marker" ] \
|
||||||
|
&& ! openssl verify -CAfile "$root" "$ca" >/dev/null 2>&1; then
|
||||||
|
${
|
||||||
|
if swarmCaCfg.autoConfigure then
|
||||||
|
''
|
||||||
|
echo "adopting the swarm CA: $ca does not chain to $root" >&2
|
||||||
|
# Keep the old CA as an anchor across the overlap. Consumers
|
||||||
|
# read the bundle, so adoption is additive before it is
|
||||||
|
# subtractive — agents pick up new trust only when their
|
||||||
|
# container restarts, which is a window even on one host.
|
||||||
|
cp "$ca" "$prev"
|
||||||
|
chmod 0644 "$prev"
|
||||||
|
rm -f "$ca" "$cak"
|
||||||
|
touch "$marker"''
|
||||||
|
else
|
||||||
|
''
|
||||||
|
echo "hive-tls: this hive's CA does not chain to the swarm root." >&2
|
||||||
|
echo " hive CA: $ca" >&2
|
||||||
|
echo " swarm root: $root" >&2
|
||||||
|
echo "Adopting the hierarchy is not automatic here: it invalidates an" >&2
|
||||||
|
echo "anchor that peers and agents on OTHER hosts still trust, and they" >&2
|
||||||
|
echo "refresh on their own schedule — only you know when that is safe." >&2
|
||||||
|
echo "To adopt: rm $ca $cak && systemctl restart hive-tls-ca.service" >&2
|
||||||
|
echo "To keep the current CA deliberately: touch $marker" >&2
|
||||||
|
exit 1''
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# --- CA: generated once and reused across leaf rotations, in
|
# --- CA: generated once and reused across leaf rotations, in
|
||||||
# whichever of the two shapes `caGenScript` selected.
|
# whichever of the two shapes `caGenScript` selected.
|
||||||
# Regenerated only if missing or already expired (checkend 0).
|
# Regenerated only if missing or already expired (checkend 0).
|
||||||
|
|
@ -298,12 +341,24 @@ in
|
||||||
# Written IN PLACE, never renamed into position: containers bind
|
# Written IN PLACE, never renamed into position: containers bind
|
||||||
# -mount this file, and a bind mount follows the inode. A
|
# -mount this file, and a bind mount follows the inode. A
|
||||||
# rename would leave every consumer holding the old one.
|
# rename would leave every consumer holding the old one.
|
||||||
|
# `ca-previous.pem` rides along after an adoption for the same
|
||||||
|
# reason: a consumer that has not restarted yet is still pinned to
|
||||||
|
# the old anchor, and dropping it the instant the new one exists
|
||||||
|
# would break exactly the hosts the migration was supposed to
|
||||||
|
# spare. Removing it is a deliberate later step, not this unit's
|
||||||
|
# call — "long enough" is a deployment fact.
|
||||||
|
#
|
||||||
|
# Built as an explicit anchor ARRAY with `if` guards, not
|
||||||
|
# `[ -s f ] && anchors+=(f)`: under `set -e` an && list whose test
|
||||||
|
# fails IS a failing command and kills the unit, so the tidier
|
||||||
|
# form would break precisely on the hive where the optional file
|
||||||
|
# is legitimately absent. The `|| true` patch for that is worse —
|
||||||
|
# it would also swallow a real failure to read the hive CA.
|
||||||
bundle="$d/trust-bundle.pem"
|
bundle="$d/trust-bundle.pem"
|
||||||
if [ -s "$root" ]; then
|
anchors=("$ca")
|
||||||
cat "$ca" "$root" > "$bundle"
|
if [ -s "$prev" ]; then anchors+=("$prev"); fi
|
||||||
else
|
if [ -s "$root" ]; then anchors+=("$root"); fi
|
||||||
cat "$ca" > "$bundle"
|
cat "''${anchors[@]}" > "$bundle"
|
||||||
fi
|
|
||||||
chmod 0644 "$bundle"
|
chmod 0644 "$bundle"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue