fix(#3462): re-sign a leaf when it stops covering the configured names
Expiry was the only re-sign trigger, so a leaf signed when the name set was smaller stayed valid -- and wrong -- for its whole lifetime. Adding a service to swarm.serviceDomains reissues the sub-CA (its own .names reconciliation) but nothing regenerated the leaf nginx actually serves, which left the previous commit's config change unable to fix anything on a hive whose gateway leaf was not near expiry. covers() reads the DNS names back out of the certificate rather than a sidecar file: the pem is what nginx serves, and a bookkeeping file drifts from it the moment a leaf is replaced by hand. Applied to both leaves -- the hive leaf has the same defect if the hive domain ever changes.
This commit is contained in:
parent
2dea5798e4
commit
2d2f16406f
1 changed files with 37 additions and 3 deletions
|
|
@ -514,6 +514,39 @@ in
|
|||
[ -s "$1" ] && openssl x509 -in "$1" -noout -checkend "$halflife" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Expiry is not the only way a leaf goes wrong. A leaf signed
|
||||
# when the configured name set was smaller stays valid for its
|
||||
# whole lifetime while omitting every name added since — so a
|
||||
# config change can evaluate, build and deploy cleanly and the
|
||||
# gateway still serves a certificate that does not cover the new
|
||||
# service. The services sub-CA already reconciles this way (its
|
||||
# `.names` comparison in ../swarm-ca.nix); the leaves did not, so
|
||||
# adding a name to the config alone could not fix a mis-served
|
||||
# certificate on any hive whose leaf was not already near expiry.
|
||||
#
|
||||
# Read the names out of the CERTIFICATE, not a sidecar file: the
|
||||
# pem is what nginx actually serves, and a bookkeeping file
|
||||
# drifts from it the moment anyone replaces a leaf by hand.
|
||||
covers() { # $1 = leaf, $2 = space-separated names it must carry
|
||||
[ -s "$1" ] || return 1
|
||||
_have="$(openssl x509 -in "$1" -noout -ext subjectAltName 2>/dev/null \
|
||||
| tr ',' '\n' | sed -n 's/^[[:space:]]*DNS:\(.*\)$/\1/p')"
|
||||
[ -n "$_have" ] || return 1
|
||||
_missing=0
|
||||
# ⚠️ The hive leaf carries `*.<domain>`; without this the shell
|
||||
# expands it against the cwd and the comparison silently tests
|
||||
# a filename.
|
||||
set -f
|
||||
for _n in $2; do
|
||||
printf '%s\n' "$_have" | grep -qxF "$_n" || _missing=1
|
||||
done
|
||||
set +f
|
||||
[ "$_missing" = 0 ]
|
||||
}
|
||||
|
||||
hiveNames=${lib.escapeShellArg "${domain} *.${domain}"}
|
||||
svcNames=${lib.escapeShellArg (lib.concatStringsSep " " swarmServiceDomains)}
|
||||
|
||||
# The services leaf is only expected where the sub-CA exists;
|
||||
# elsewhere its absence is the correct state, not a stale leaf.
|
||||
want_svc=${if swarmServiceDomains == [ ] then "0" else "1"}
|
||||
|
|
@ -521,12 +554,13 @@ in
|
|||
want_svc=0
|
||||
fi
|
||||
|
||||
if fresh "$leaf" && { [ "$want_svc" = 0 ] || fresh "$svcleaf"; }; then
|
||||
echo "leaves valid for more than half their lifetime — no resign needed"
|
||||
if fresh "$leaf" && covers "$leaf" "$hiveNames" \
|
||||
&& { [ "$want_svc" = 0 ] || { fresh "$svcleaf" && covers "$svcleaf" "$svcNames"; }; }; then
|
||||
echo "leaves valid, and covering the configured names — no resign needed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "a leaf is missing or near expiry — re-signing under the current CAs"
|
||||
echo "a leaf is missing, near expiry, or missing a configured name — re-signing"
|
||||
before="$(sha256sum "$leaf" "$svcleaf" 2>/dev/null || true)"
|
||||
|
||||
${signHiveLeaf}
|
||||
|
|
|
|||
Loading…
Reference in a new issue