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>
84 lines
3.4 KiB
Bash
84 lines
3.4 KiB
Bash
#!/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
|