Files
mqtt-security-test/docker
RyanandClaude Sonnet 5.5 20d69b93b7 Use plain Mermaid diagrams again and drop the SVG images
The preview problems were caused by the diagram text, which is now fixed in
every copy, so the pre-rendered SVGs (white background, harder to keep in
sync) are no longer needed. Also updates docker/docs with the fixed text.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 11:31:29 +03:30
..

Dockerized enrollment demo

Three programs talk through a real Mosquitto 2.1 broker with Dynamic Security:

Service Language / library Role
backend Go (stdlib crypto, paho MQTT) provisions Dynamic Security, answers enrollment, creates the FN broker client
board-c C, libsodium + libmosquitto simulated board
board-cpp C++17, OpenSSL 3 + libmosquitto simulated board
broker eclipse-mosquitto:2.1-alpine anonymous enrollment listener + Dynamic Security

Web view

Open http://localhost:8099 after starting the stack. It shows the three programs as side-by-side lanes, the current step in plain language, a progress card per board (checks passed, password fingerprint match) and the full timeline. The viewer service tails the LOG_FILE of each program from a shared volume. Options: follow live, only checks and steps, hide countdown lines. Click a long hex value to expand it.

Run again button (only when started with compose.restart.yaml, which run-docker.sh does): stops the backend and boards, restarts the broker (fresh Dynamic Security state), clears the old logs and starts everything again, so the whole run replays with its countdowns. It works by letting the viewer call the Docker API through the mounted /var/run/docker.sock, which gives that container control of Docker on this machine. The page is bound to 127.0.0.1 only and the endpoint requires a custom header, but this is for demo use only: never expose port 8099.

Run

./run-docker.sh              # builds, starts, streams logs, prints the result
docker compose logs -f       # follow logs from another terminal
docker compose down          # stop everything

Timing (change in compose.yaml): the backend waits 15 s, board-c 20 s, board-cpp 35 s, and every step pauses STEP_DELAY = 2 s so the logs can be followed.

What each board does (19 checks, exit code 0 = all passed)

  1. random Client ID, X25519 key pair, nonce
  2. anonymous connect; own enroll/response/<cid> granted, other boards' and wildcard DENIED
  3. publish request; backend derives the password, creates client FN-xxxxxx (Client ID bound), signs the reply
  4. verify the Ed25519 signature with the pinned public key; tampered signature, other nonce, swapped FN rejected
  5. derive the same password locally (compare the logged fingerprints; the password is never logged or sent)
  6. wrong password refused; right password with a different Client ID refused; correct login accepted
  7. FN topics only: own board/<FN>/cmd granted, other FN's denied, hello accepted and acknowledged

Findings from building it

  • %c / %u substitution in Dynamic Security ACLs does not work in Mosquitto 2.0.22 (literal ACLs did, substituted ones silently never matched). It works in 2.1.x, so this demo uses 2.1.
  • Changing a connected client's roles (addClientRole) disconnects that client, so the backend uses a separate admin client (control API) and service client (backend-svc).
  • mosquitto_ctrl addRoleACL needs an explicit allow/deny, otherwise it fails with "Invalid arguments".
  • Go dependencies are vendored because the Docker build could not reach the Go module proxy here.
  • This spike has no TLS and uses demo secrets from compose.yaml.