diff --git a/scripts/test-mqtt-broker.sh b/scripts/test-mqtt-broker.sh new file mode 100644 index 0000000..9f03975 --- /dev/null +++ b/scripts/test-mqtt-broker.sh @@ -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