Findings from a full review (auth/API, CA and rack channel, config generation, integrations, UI/agents/scripts), each verified in code: - rack binary endpoint read any region file via arch=../.. (CA key) - render endpoint traversal; machine-reported MACs used as file paths - delegated intermediates could mint admin/rack identities and IPs; identities now require root-issued certs, intermediates are ServerAuth-only with both name types constrained - rack certs were ServerAuth for rack-chosen SANs: now client-only; racks accept only the region's own cert; join checks the CA fingerprint; unbound join codes cannot take an existing name; job results bound to the rack that took them - rack self-update: region signs binaries with the CA key, the root unit verifies against the join-time CA in a root-owned dir - dnsmasq reads a checked, root-owned copy limited to generated options - commissioning could overwrite deployed machines; MAC takeover of PXE - injection into dnsmasq/cloud-init/PXE/udev/DNS via names and machine-reported strings; stored XSS in the UI; CSP and cross-origin protection; deploy agent shell injection and seed traversal - Redfish requests confined to the BMC's /redfish/ tree - admin_http defaults to loopback; body limits and timeouts; secrets off command lines Also: IPAM claim/reconcile race, sync stopping on one error, watchdog race, release wiping the wrong disk, SAN LUNs as auto targets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
3.7 KiB
Bash
86 lines
3.7 KiB
Bash
#!/bin/sh
|
|
# Called by rollcall-dnsmasq.path when rollcall rewrites dnsmasq's files.
|
|
#
|
|
# Host files (leases, names) only need a HUP. When the generated configuration
|
|
# changed, dnsmasq is restarted. If it does not come back (a conflicting
|
|
# hand-written file in /etc/dnsmasq.d, for instance - `dnsmasq --test` misses
|
|
# some of those), the last configuration that worked is put back and dnsmasq
|
|
# is started with it, so DHCP stays up. The rejected content is kept next to it
|
|
# and not tried again until it changes.
|
|
#
|
|
# rollcall (an unprivileged user) writes $SRC; dnsmasq starts as root and reads
|
|
# only the copy in $LIVE, a directory of root's. The copy is made without
|
|
# following links and holds only the options rollcall generates: nothing that
|
|
# runs a program, includes another file or changes dnsmasq's user.
|
|
set -u
|
|
SRC=/var/lib/rollcall/dnsmasq/conf.d/rollcall.conf
|
|
LIVE=/var/lib/rollcall-dnsmasq
|
|
CONF=$LIVE/rollcall.conf
|
|
GOOD=$LIVE/rollcall.conf.accepted
|
|
BAD=$LIVE/rollcall.conf.rejected
|
|
STAMP=/run/rollcall-dnsmasq.sum
|
|
ALLOWED='addn-hosts bind-dynamic bogus-priv dhcp-authoritative dhcp-boot dhcp-hostsfile dhcp-match dhcp-option dhcp-range domain domain-needed enable-ra enable-tftp expand-hosts interface local log-dhcp no-resolv pxe-service ra-param server tftp-root'
|
|
|
|
install -d -m 0755 -o root -g root "$LIVE"
|
|
umask 022
|
|
sum() { cat "$1" 2>/dev/null | sha256sum | cut -d' ' -f1; }
|
|
put() { cp "$1" "$CONF.tmp" && mv -f "$CONF.tmp" "$CONF"; } # within $LIVE: root's
|
|
restore() { # put the last working configuration back (or none)
|
|
if [ -s "$GOOD" ]; then put "$GOOD"; else rm -f "$CONF"; fi
|
|
sum "$CONF" > "$STAMP"
|
|
}
|
|
|
|
# take rollcall's file: a regular file, not a link, at most 1 MB
|
|
cand=$LIVE/candidate.tmp
|
|
rm -f "$cand"
|
|
if [ -e "$SRC" ]; then
|
|
if ! dd if="$SRC" of="$cand" iflag=nofollow bs=1M count=1 status=none 2>/dev/null; then
|
|
echo "$SRC is not a regular file; ignored" >&2
|
|
exit 1
|
|
fi
|
|
printf '%s\n' $ALLOWED > "$LIVE/allowed.tmp"
|
|
bad=$(grep -vE '^[[:space:]]*(#|$)' "$cand" | sed -E 's/^[[:space:]]*([^=[:space:]]*).*/\1/' | sort -u |
|
|
grep -vxFf "$LIVE/allowed.tmp" | tr '\n' ' ')
|
|
rm -f "$LIVE/allowed.tmp"
|
|
if [ -n "$bad" ]; then
|
|
rm -f "$cand"
|
|
echo "generated dnsmasq configuration has options rollcall does not write ($bad); not used" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
new=$(sum "$cand")
|
|
if [ "$new" = "$(cat "$STAMP" 2>/dev/null)" ] && { [ -e "$CONF" ] || [ ! -e "$cand" ]; }; then
|
|
rm -f "$cand"
|
|
systemctl kill -s HUP dnsmasq.service
|
|
exit 0
|
|
fi
|
|
if [ -e "$cand" ] && [ "$new" = "$(sum "$BAD")" ]; then
|
|
rm -f "$cand"
|
|
restore
|
|
echo "generated dnsmasq configuration was rejected before and has not changed; keeping the last working one" >&2
|
|
exit 1
|
|
fi
|
|
if [ -e "$cand" ]; then mv -f "$cand" "$CONF"; else rm -f "$CONF"; fi
|
|
if out=$(dnsmasq --test 2>&1) && systemctl restart dnsmasq.service; then
|
|
echo "$new" > "$STAMP"
|
|
if [ -e "$CONF" ]; then cp "$CONF" "$GOOD"; else rm -f "$GOOD"; fi
|
|
rm -f "$BAD"
|
|
echo "dnsmasq restarted: generated configuration changed"
|
|
exit 0
|
|
fi
|
|
why=$(journalctl -u dnsmasq.service -n 5 --no-pager -o cat 2>/dev/null | grep -v '^$' | tail -3 | tr '\n' ' ')
|
|
[ -e "$CONF" ] && cp "$CONF" "$BAD"
|
|
restore
|
|
if systemctl restart dnsmasq.service; then
|
|
echo "generated dnsmasq configuration rejected ($out $why); dnsmasq runs with the last working one, the rejected file is $BAD" >&2
|
|
exit 1
|
|
fi
|
|
# not even that starts: something outside rollcall's file conflicts. Run dnsmasq
|
|
# without rollcall's file so whatever else is configured keeps working.
|
|
rm -f "$CONF"
|
|
sum "$CONF" > "$STAMP"
|
|
systemctl restart dnsmasq.service || true
|
|
echo "dnsmasq does not start with rollcall's configuration ($why); started without it - check /etc/dnsmasq.conf and /etc/dnsmasq.d for conflicting options" >&2
|
|
exit 1
|