Go MQTT Vending Backend ยท Lesson 11B ยท Optional extension

Provision per-device broker permissions at runtime

Use Mosquitto Dynamic Security to create device clients and roles without editing static password and ACL files.

๐ŸŽฏ One skill: separate provisioning from runtime traffic๐Ÿง  Predict revocation behavior๐Ÿงช Isolated broker config

โ“ Start with the provisioning story

A new vending machine is installed after Mosquitto starts. How can it receive an individual credential and narrow topic permissions while the broker is running?

Name the privileged actor and the separate device identity before continuing.

๐Ÿงญ Three identities, three responsibilities

  1. Provisioning operator/service: authenticates with a protected management identity and uses the Dynamic Security control API.
  2. Device client: receives its own account and a role limited to its topic namespace.
  3. Backend: receives ordinary device publications, validates their contracts, and applies application policy. It should not hold broker-administrator credentials just to process traffic.

Dynamic Security is a Mosquitto plugin and broker control API, not a Go MQTT 5 library feature. The plugin manages clients, groups, roles, and ACLs and persists its configuration state. The ordinary backend/device listener and a separate management identity keep operations clearly scoped.

๐Ÿง  Check the boundary: Which credential should the device use to publish telemetry?




๐Ÿงฑ Keep the extension isolated

The normal course lab uses Mosquitto password and ACL files and does not load this plugin. Create a separate broker configuration and persistent, broker-writable Dynamic Security state file. Verify the plugin path inside the pinned image. Do not silently mix plugin-managed accounts with the existing static authentication setup.

๐Ÿ“„ Complete broker configuration: lab/dynsec/mosquitto.conf

File overview: This isolated configuration loads only Dynamic Security for the lab listener, disables anonymous access, and stores plugin state under the persistent broker data mount. The Linux plugin path below is the standard Debian multiarch path; confirm it inside the exact image before starting the comparison broker because the container build may package it elsewhere.

๐Ÿงฉ Full configuration file
listener 1883
allow_anonymous false
per_listener_settings false
plugin /usr/lib/x86_64-linux-gnu/mosquitto_dynamic_security.so
plugin_opt_config_file /mosquitto/data/dynamic-security.json
persistence true
persistence_location /mosquitto/data/
log_dest stdout
connection_messages true

Before writing that file, inspect the pinned image's plugin path with a one-off container command such as docker run --rm --entrypoint sh eclipse-mosquitto:2.0.22 -c 'find /usr -name mosquitto_dynamic_security.so -print'. The command searches inside the image and removes the temporary container; it does not start or modify the course broker. If the path differs, update the single plugin line and record the observed path. I could not inspect this host's Docker image because the Docker socket denied access.

๐Ÿชช Complete device and backend role rules

File overview: These are the full topic permissions to install through the management API. Device ACL patterns bind the authenticated username as one device-ID segment. Backend permissions are fleet scoped. Set receive default-deny explicitly before grants.

๐Ÿ“‹ Role matrix and complete control commands
# These commands run against the dedicated Dynamic Security broker.
# Supply the admin secret through your protected local shell/session.
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec setDefaultACLAccess publishClientReceive deny

mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec createRole device
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addRoleACL device publishClientSend 'lab/v1/devices/%u/telemetry' allow 10
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addRoleACL device publishClientSend 'lab/v1/devices/%u/responses' allow 10
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addRoleACL device subscribePattern 'lab/v1/devices/%u/commands' allow 10
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addRoleACL device publishClientReceive 'lab/v1/devices/%u/commands' allow 10

mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec createRole backend
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addRoleACL backend publishClientSend 'lab/v1/devices/+/commands' allow 10
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addRoleACL backend subscribePattern 'lab/v1/devices/+/telemetry' allow 10
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addRoleACL backend publishClientReceive 'lab/v1/devices/+/telemetry' allow 10

mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec createClient device-001 -i lab-device-001
mosquitto_ctrl -h 127.0.0.1 -p 18883 -u dynsec-admin -P "$DYNSEC_ADMIN_PASSWORD" \
  dynsec addClientRole device-001 device 10

Run the commands in order and only once per role/ACL. The client-creation command prompts for the device password. For a repeated setup, query existing plugin state first and handle already-created roles/ACLs explicitly rather than widening access.

Start with default-deny behavior for the relevant direction, grant only the required device publish/backend subscribe paths, and test real message delivery. A successful SUBACK or an ACL line alone does not prove a cross-device publication is blocked.

๐Ÿงช Provision, test, revoke

  1. Create the isolated plugin configuration and initialize its state using the secured management procedure documented in the full lesson.
  2. Provision one test device account and a least-privilege role. Connect it and verify its own telemetry succeeds.
  3. Attempt cross-device publish and subscription, then send a permitted topic with a mismatched payload identity. Identify whether the broker or application rejects each case.
  4. Revoke/disable the client and observe behavior for both a new connection and an already-connected device.
  5. Restart the broker and confirm the plugin state persists from its dedicated volume.

โš ๏ธ What this experiment does not prove

Successful provisioning does not establish secure remote administration unless transport and management access are protected. A plugin role does not replace application schema checks. Revoking an account may not by itself terminate every existing connection; verify the live-client behavior for the pinned broker/plugin.

โœ… Done when

You can trace provisioning, broker authentication, topic authorization, application validation, and revocation as distinct steps. The normal static lab remains available as a separate configuration.

๐Ÿ“š Full failure and revocation experiments: Section 11B source lesson. Primary references: the official Mosquitto Dynamic Security guide and mosquitto_ctrl DynSec manual.

Ask me to troubleshoot a denied operation using its client reason and broker log evidence.