Add MQTT broker smoke-test script

Creates a throwaway dynsec client+role scoped to a private topic,
publishes/subscribes to confirm round-trip delivery, checks a bogus
login is rejected, then cleans up. Syntax verified against a real
eclipse-mosquitto:2 + dynamic-security broker locally before writing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-28 13:58:25 +00:00
parent 7e9fe62e1c
commit 8b2dce9714

View file

@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Smoke-test the shared MQTT broker (LXC 104 · hotel-manage-mqtt-broker).
#
# Verifies the broker actually authenticates and round-trips messages, not
# just that the process is up: creates a throwaway dynsec client + role
# scoped to a private "smoketest/#" topic, publishes a retained message,
# confirms it's delivered back via subscribe, then deletes the throwaway
# client/role again. Also checks that a bogus login is rejected, proving
# allow_anonymous=false is actually enforced.
#
# Run on the Proxmox host:
# bash <(curl -fsSL https://git.pterois.co.uk/hotel-manage-stack/stack-init/raw/branch/main/scripts/test-mqtt-broker.sh)
set -euo pipefail
CREDS_FILE=/root/hotel-manage-credentials.txt
[[ -f "$CREDS_FILE" ]] || { echo "No credentials file at $CREDS_FILE — has the stack been installed?"; exit 1; }
MQTT_ADMIN_USER=$(sed -n 's/^MQTT_ADMIN_USER=//p' "$CREDS_FILE" | head -1)
MQTT_ADMIN_PASS=$(sed -n 's/^MQTT_ADMIN_PASS=//p' "$CREDS_FILE" | head -1)
[[ -n "$MQTT_ADMIN_USER" && -n "$MQTT_ADMIN_PASS" ]] || {
echo "MQTT_ADMIN_USER/PASS not found in $CREDS_FILE — has the broker been deployed (--only mqtt-broker)?"
exit 1
}
pct exec 104 -- docker inspect hotel-manage-mqtt-broker &>/dev/null || {
echo "Container hotel-manage-mqtt-broker not found on LXC 104 — is it deployed?"
exit 1
}
NET="--network container:hotel-manage-mqtt-broker"
IMG="eclipse-mosquitto:2"
TEST_USER="smoketest-$$"
TEST_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c 18)
TEST_ROLE="smoketest-role-$$"
TOPIC="smoketest/$$"
PAYLOAD="hello-$$"
ctrl() {
pct exec 104 -- docker run --rm $NET "$IMG" \
mosquitto_ctrl -h 127.0.0.1 -p 1883 -u "$MQTT_ADMIN_USER" -P "$MQTT_ADMIN_PASS" dynsec "$@" 2>/dev/null
}
cleanup() {
ctrl deleteClient "$TEST_USER" &>/dev/null || true
ctrl deleteRole "$TEST_ROLE" &>/dev/null || true
}
trap cleanup EXIT
echo "== Creating throwaway client + role scoped to ${TOPIC} =="
ctrl createClient "$TEST_USER" -p "$TEST_PASS"
ctrl createRole "$TEST_ROLE"
ctrl addRoleACL "$TEST_ROLE" publishClientSend "$TOPIC" allow
ctrl addRoleACL "$TEST_ROLE" publishClientReceive "$TOPIC" allow
ctrl addRoleACL "$TEST_ROLE" subscribePattern "$TOPIC" allow
ctrl addClientRole "$TEST_USER" "$TEST_ROLE"
echo "== Publishing retained test message =="
pct exec 104 -- docker run --rm $NET "$IMG" \
mosquitto_pub -h 127.0.0.1 -p 1883 -u "$TEST_USER" -P "$TEST_PASS" -t "$TOPIC" -m "$PAYLOAD" -r
echo "== Subscribing to confirm delivery =="
RESULT=$(pct exec 104 -- docker run --rm $NET "$IMG" \
mosquitto_sub -h 127.0.0.1 -p 1883 -u "$TEST_USER" -P "$TEST_PASS" -t "$TOPIC" -C 1 -W 10 2>/dev/null || true)
echo "== Clearing retained message =="
pct exec 104 -- docker run --rm $NET "$IMG" \
mosquitto_pub -h 127.0.0.1 -p 1883 -u "$TEST_USER" -P "$TEST_PASS" -t "$TOPIC" -n -r
echo "== Confirming a bogus login is rejected (allow_anonymous=false) =="
if pct exec 104 -- docker run --rm $NET "$IMG" \
mosquitto_pub -h 127.0.0.1 -p 1883 -u "not-a-real-user" -P "wrong-password" -t "$TOPIC" -m "x" &>/dev/null; then
echo "FAIL — bogus credentials were accepted, broker auth is not enforced"
exit 1
fi
echo " ok — bogus credentials rejected"
if [[ "$RESULT" == "$PAYLOAD" ]]; then
echo
echo "PASS — broker authenticated the throwaway client and round-tripped '$PAYLOAD'"
else
echo
echo "FAIL — expected payload '$PAYLOAD', got: '$RESULT'"
exit 1
fi