Compare commits

...
Author SHA1 Message Date
atlas
893b686c15 fix(#3527): a missing source must report as zero, not as empty
argus, reviewing, ran the guard against a source path that does not
exist rather than one that is empty. grep writes nothing to stdout in
that case, so `|| true` left the count variable empty and the -eq test
died with "integer expected" instead of reporting.

The unit still failed — cat hits the same missing file and set -e stops
it — but with a generic "no such file" rather than the message naming
which half is absent, which is the only thing this guard is for.

`|| echo 0` on all three counts. The gate gained the arm that was
missing: an absent source, asserted to fail THROUGH the guard rather
than merely to fail.
2026-08-19 20:27:00 +02:00
atlas
40557ffb74 fix(#3527): count the hive half, not the assembled bundle
The guard inspected the assembled file for any certificate. The system
store always holds certificates, so it passed unconditionally — including
in the one case it was written to catch, where the hive CA half
contributed nothing.

That half is the only one that matters here: every name these consumers
verify is issued by our own CA, so a bundle of nothing but public CAs is,
for this purpose, an empty bundle that measures as full. The failure is
silent and total — the unit reports success and every egress TLS call to
a swarm service then fails.

Counts the source on its own before assembling, and checks the result
carries what both halves brought, so a source truncated between the count
and the copy is caught too.

Scope is stated at the guard: it proves the anchor was contributed, not
that it is usable. A consumer reading only the first certificate ignores
it regardless, which is what took the swarm collector down, and no check
on this file can see that. Only a handshake can.
2026-08-19 20:16:16 +02:00

View file

@ -170,13 +170,53 @@ in
set -euo pipefail
install -d -m 0755 ${dir}
tmp=${bundlePath}.tmp
cat /etc/ssl/certs/ca-certificates.crt ${source} > "$tmp"
# `cat` of an empty or missing-but-mounted source exits 0, so the
# result has to be inspected rather than the command trusted.
if ! grep -q 'BEGIN CERTIFICATE' "$tmp"; then
echo "${unit}: assembled bundle contains no certificate" >&2
# Count the HIVE half on its own, before assembling.
#
# Inspecting the assembled file cannot work: the system store
# always holds certificates, so "does the result contain a
# certificate" passes unconditionally — including in the one case
# worth catching, where this source contributed nothing. And the
# public CAs are irrelevant to what this bundle is for: every name
# the consumer verifies is issued by our own CA, so a bundle of
# nothing but public CAs is, for this purpose, an empty one that
# measures as full.
#
# One helper, because the safe form is not the obvious one and
# three copies of a subtle idiom is three chances to get it wrong.
#
# `grep -c` has two different zero-ish outcomes and `set -e` is
# on: an EMPTY file prints `0` and exits 1, a MISSING file prints
# nothing and exits 2. So `|| true` leaves the variable empty on
# the second — the `-eq` then dies with "integer expected"
# instead of reporting — and `|| echo 0` appends a second zero on
# the first, yielding `00`. Normalising afterwards is the only
# form that survives both.
certs() {
n=$(grep -c 'BEGIN CERTIFICATE' "$1" 2>/dev/null || true)
if [ -z "$n" ]; then n=0; fi
printf '%s' "$n"
}
contributed=$(certs ${source})
if [ "$contributed" -eq 0 ]; then
echo "${unit}: ${source} contributed no certificate to the bundle" >&2
exit 1
fi
system=$(certs /etc/ssl/certs/ca-certificates.crt)
cat /etc/ssl/certs/ca-certificates.crt ${source} > "$tmp"
# `cat` of a source truncated between the count and the copy still
# exits 0, so the result is checked against what both halves
# brought rather than merely for being non-empty.
total=$(certs "$tmp")
if [ "$total" -ne $((system + contributed)) ]; then
echo "${unit}: bundle has $total certificates, expected $system + $contributed" >&2
exit 1
fi
# ⚠️ Scope: this proves the anchor was CONTRIBUTED, not that it is
# USABLE. A consumer that reads only the first certificate of the
# file ignores it anyway — that is what took the swarm collector
# down — and no check on this file can see it. Only a real
# handshake can. Do not read a green assembly as a working trust
# store.
chmod 0644 "$tmp"
mv "$tmp" ${bundlePath}
'';