deploy: harden mosquitto password_file guards (non-root, runuser errors, symlinks, traversal, all lines)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqhjP7nud5gdBg25zCiRk9
This commit is contained in:
@@ -266,13 +266,41 @@ mosquitto_conf() { # path of mosquitto.conf; the test knob is honoured only whe
|
||||
echo "$c"
|
||||
}
|
||||
|
||||
mosquitto_pwfile_problem() { # [mosquitto.conf] — prints the password_file path when it exists but the mosquitto user cannot read it; else nothing
|
||||
local conf=${1:-$(mosquitto_conf)} user=mosquitto pw
|
||||
mosquitto_pwfile_paths() { # [mosquitto.conf] — every password_file value (mosquitto.conf + conf.d beside it), comments ignored, quotes stripped, absolute paths only
|
||||
local conf=${1:-$(mosquitto_conf)} p
|
||||
while IFS= read -r p; do
|
||||
p=${p#\"}; p=${p%\"}
|
||||
case "$p" in /*) echo "$p" ;; esac
|
||||
done < <({ cat "$conf" "$(dirname "$conf")"/conf.d/*.conf 2>/dev/null || true; } | awk '/^[[:space:]]*password_file[[:space:]]/ { print $2 }')
|
||||
}
|
||||
|
||||
mosquitto_can_probe() { # runuser needs root; a test knob (VANLINK_MOSQUITTO_CONF/_USER) allows a stub runuser as non-root
|
||||
[ "$(id -u)" = 0 ] || [ -n "${VANLINK_MOSQUITTO_CONF:-}${VANLINK_MOSQUITTO_USER:-}" ]
|
||||
}
|
||||
|
||||
mosquitto_pwfile_scan() { # [mosquitto.conf] — one line per problem: "unreadable <resolved file>" | "notraverse <dir>". Silent when it cannot check.
|
||||
local conf=${1:-$(mosquitto_conf)} user=mosquitto p f d rc
|
||||
mosquitto_can_probe || return 0
|
||||
[ "$(id -u)" = 0 ] || user=${VANLINK_MOSQUITTO_USER:-$user}
|
||||
pw=$({ cat "$conf" "$(dirname "$conf")"/conf.d/*.conf 2>/dev/null || true; } | awk '/^[[:space:]]*password_file[[:space:]]/ { print $2 }' | tail -n1) || pw=
|
||||
[ -n "$pw" ] && [ -e "$pw" ] || return 0
|
||||
id "$user" >/dev/null 2>&1 && command -v runuser >/dev/null 2>&1 || return 0
|
||||
runuser -u "$user" -- test -r "$pw" >/dev/null 2>&1 || echo "$pw"
|
||||
runuser -u "$user" -- true >/dev/null 2>&1 || return 0 # PAM/session trouble: cannot judge
|
||||
while IFS= read -r p; do
|
||||
f=$(readlink -f -- "$p" 2>/dev/null) || continue
|
||||
[ -f "$f" ] || continue
|
||||
d=$(dirname "$f")
|
||||
rc=0; runuser -u "$user" -- test -x "$d" >/dev/null 2>&1 || rc=$?
|
||||
if [ "$rc" != 0 ]; then
|
||||
[ "$rc" -ge 125 ] && [ "$rc" -le 127 ] || echo "notraverse $d"
|
||||
continue
|
||||
fi
|
||||
rc=0; runuser -u "$user" -- test -r "$f" >/dev/null 2>&1 || rc=$?
|
||||
if [ "$rc" != 0 ] && { [ "$rc" -lt 125 ] || [ "$rc" -gt 127 ]; }; then echo "unreadable $f"; fi
|
||||
done < <(mosquitto_pwfile_paths "$conf")
|
||||
return 0
|
||||
}
|
||||
|
||||
mosquitto_pwfile_problem() { # [mosquitto.conf] — prints each password_file (resolved) the mosquitto user cannot read; else nothing
|
||||
mosquitto_pwfile_scan "$@" | sed -n 's/^unreadable //p'
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -363,14 +391,22 @@ post_check() { # read-only live checks; every probe is guarded, warns instead o
|
||||
connected) echo " ok van-router-mqtt: MQTT connection ok" ;;
|
||||
esac
|
||||
# g2b. mosquitto (the base router-status broker): unit active, password file readable by the broker's user
|
||||
local mu mpw
|
||||
local mu mpw mk mv
|
||||
mu=$(systemctl show -p ActiveState --value mosquitto 2>/dev/null) || mu=
|
||||
if [ "$mu" = active ]; then echo " ok unit mosquitto active"
|
||||
else warn "post-deploy: unit mosquitto is not active (ActiveState=${mu:-?})"; fi
|
||||
mpw=$(mosquitto_pwfile_problem)
|
||||
if [ -n "$mpw" ]; then
|
||||
warn "post-deploy: mosquitto password file $mpw is not readable by the mosquitto user — the broker will fail to start on its next restart (fix: chown mosquitto:mosquitto $mpw; chmod 0600 $mpw)"
|
||||
else echo " ok mosquitto password file readable (or none configured)"; fi
|
||||
if ! mosquitto_can_probe; then
|
||||
[ -z "$(mosquitto_pwfile_paths)" ] || echo " ok mosquitto password file: cannot verify as non-root (run with sudo)"
|
||||
else
|
||||
mpw=$(mosquitto_pwfile_scan)
|
||||
if [ -z "$mpw" ]; then echo " ok mosquitto password file readable (or none configured)"; fi
|
||||
while read -r mk mv; do
|
||||
case "$mk" in
|
||||
unreadable) warn "post-deploy: mosquitto password file $mv is not readable by the mosquitto user — the broker will fail to start on its next restart (fix: chown mosquitto:mosquitto $mv; chmod 0600 $mv)" ;;
|
||||
notraverse) warn "post-deploy: mosquitto cannot traverse $mv to reach its password file" ;;
|
||||
esac
|
||||
done <<<"$mpw"
|
||||
fi
|
||||
if [ "$ENABLE_LEDS" = 1 ]; then
|
||||
# g3. front LEDs (van-leds): each configured LED exists; the static ones carry the expected trigger.
|
||||
# The WAN LED follows the uplink, so its state is only reported, never warned about.
|
||||
@@ -1266,11 +1302,17 @@ systemctl daemon-reexec
|
||||
# reboot), deliberately not forced here (reconnecting a WAN flaps it).
|
||||
systemctl restart systemd-resolved || warn "systemd-resolved failed to restart (journalctl -xeu systemd-resolved)"
|
||||
# A password_file the mosquitto user cannot read (seen: root:root 0700) makes the restart fail and the broker stay down.
|
||||
mq_pw=$(mosquitto_pwfile_problem /etc/mosquitto/mosquitto.conf)
|
||||
if [ -n "$mq_pw" ]; then
|
||||
chown mosquitto:mosquitto "$mq_pw" && chmod 0600 "$mq_pw"
|
||||
warn "mosquitto password file $mq_pw was not readable by the mosquitto user — fixed ownership (mosquitto:mosquitto 0600)"
|
||||
fi
|
||||
while read -r mq_k mq_pw; do
|
||||
case "$mq_k" in
|
||||
unreadable)
|
||||
if chown mosquitto:mosquitto "$mq_pw" && chmod 0600 "$mq_pw"; then
|
||||
warn "mosquitto password file $mq_pw was not readable by the mosquitto user — fixed ownership (mosquitto:mosquitto 0600)"
|
||||
else
|
||||
warn "mosquitto password file $mq_pw was not readable by the mosquitto user and could NOT be fixed (do it manually: chown mosquitto:mosquitto $mq_pw; chmod 0600 $mq_pw)"
|
||||
fi ;;
|
||||
notraverse) warn "mosquitto cannot traverse $mq_pw to reach its password file" ;;
|
||||
esac
|
||||
done < <(mosquitto_pwfile_scan /etc/mosquitto/mosquitto.conf)
|
||||
systemctl restart mosquitto || warn "mosquitto failed to restart (journalctl -xeu mosquitto) — MQTT (Home Assistant, Li3, router status) is down"
|
||||
# networkd here owns only the AP radios + bridge + wired LAN port (no real uplink);
|
||||
# its wait-online would just stall network-online.target. NM-wait-online covers WANs.
|
||||
|
||||
Reference in New Issue
Block a user