swarm-bao: fail the unit when the services root cannot be read, instead of replacing it

A failed `bao read` of the services root made the checkend pipeline
non-zero, so the unit deleted a working root and minted a new trust
anchor. A failed `bao list` of the issuers likewise looked like an empty
mount. Both now fail the unit, which retries on its own restart budget;
the root is replaced only when openssl parsed the returned certificate
and -checkend said it expires inside a leaf's window.

Closes #4663
This commit is contained in:
atlas 2026-09-24 11:00:20 +02:00
commit 5b54b6ddc8

View file

@ -1893,11 +1893,11 @@ in
# The mount's own issuer list is the only answer that cannot be # The mount's own issuer list is the only answer that cannot be
# stale. # stale.
# #
# `bao list ${servicesPkiMountPath}/issuers` is a 404 (non-zero) on a # `bao list` exits 2 both on a mount with no issuer and on a request
# mount with no issuer and a key list once one exists, so its exit # that failed, so its exit status cannot tell them apart. Under
# status IS the question — no output parsing, no error string to # `-format=json` only the empty mount prints `{}`. The default
# recognise. The default issuer is what `${servicesPkiMountPath}/issue/` # issuer is what `${servicesPkiMountPath}/issue/` signs with, and
# signs with, and there is exactly one. # there is exactly one.
# #
# "Never twice" is the rule; "an issuer that cannot issue" is not a # "Never twice" is the rule; "an issuer that cannot issue" is not a
# case it was written for. A root with less than a leaf's window left # case it was written for. A root with less than a leaf's window left
@ -1907,10 +1907,18 @@ in
# the asymmetry: this replaces a root that is already useless, and # the asymmetry: this replaces a root that is already useless, and
# still never touches one a leaf can be issued under. # still never touches one a leaf can be issued under.
regenerate=0 regenerate=0
if ! bao list ${lib.escapeShellArg "${servicesPkiMountPath}/issuers"} >/dev/null 2>&1; then if issuers="$(bao list -format=json ${lib.escapeShellArg "${servicesPkiMountPath}/issuers"})"; then
regenerate=1 # A root this unit could not read is not known to be useless, so
elif ! bao read -field=certificate ${lib.escapeShellArg "${servicesPkiMountPath}/cert/ca"} \ # every step before `-checkend` fails the unit, and the unit retries.
| openssl x509 -noout -checkend ${toString servicesPkiLeafTtlSeconds} >/dev/null 2>&1; then if ! root_ca="$(bao read -field=certificate ${lib.escapeShellArg "${servicesPkiMountPath}/cert/ca"})"; then
echo "could not read the ${servicesPkiMountPath} root — leaving it in place" >&2
exit 1
fi
if ! openssl x509 -noout <<<"$root_ca" >/dev/null 2>&1; then
echo "the ${servicesPkiMountPath} root that bao returned does not parse — leaving it in place" >&2
exit 1
fi
if ! openssl x509 -noout -checkend ${toString servicesPkiLeafTtlSeconds} <<<"$root_ca" >/dev/null 2>&1; then
echo "the ${servicesPkiMountPath} root cannot outlive a ${servicesPkiLeafTtl} leaf — replacing it" >&2 echo "the ${servicesPkiMountPath} root cannot outlive a ${servicesPkiLeafTtl} leaf — replacing it" >&2
# `root` rather than the named issuer: the replacement re-uses # `root` rather than the named issuer: the replacement re-uses
# `issuer_name`, and generating into a name already in use is a # `issuer_name`, and generating into a name already in use is a
@ -1919,6 +1927,12 @@ in
bao delete ${lib.escapeShellArg "${servicesPkiMountPath}/root"} >/dev/null bao delete ${lib.escapeShellArg "${servicesPkiMountPath}/root"} >/dev/null
regenerate=1 regenerate=1
fi fi
elif [ "$issuers" = '{}' ]; then
regenerate=1
else
echo "could not list the ${servicesPkiMountPath} issuers — not minting a root over one that may exist" >&2
exit 1
fi
if [ "$regenerate" = 1 ]; then if [ "$regenerate" = 1 ]; then
echo "generating the swarm services root into the ${servicesPkiMountPath} mount" echo "generating the swarm services root into the ${servicesPkiMountPath} mount"