Files
mqtt-security-test/docs/mqtt-dynamic-security-rnd.md
RyanandClaude Sonnet 5.5 05d988f713
Test and deploy enrollment demo / test (push) Successful in 8s
Test and deploy enrollment demo / deploy (push) Successful in 1m12s
Add the design docs to the repo
Copies the SN-as-username process, the FN/SN comparison and the original
R&D notes from the backend repo, links them from the README and stops
documentation-only pushes from redeploying the demo.

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

14 KiB

MQTT device authentication and authorization R&D

Status: Design notes from the R&D discussion; no implementation authorized or performed.
Scope: Mosquitto Dynamic Security, Go backend, database identity, device provisioning, and ACL lifecycle.

Goal

Give each board only the MQTT access it needs, move a vending machine's operational broker identity with that vending machine when hardware is replaced, and make broker-side changes traceable and reconcilable with the database. Keep authentication (who may connect) distinct from authorization (which MQTT actions and topics are allowed).

Domain terms

Term Meaning in this design
FN Backend-assigned identity for a physical board. It follows the board.
SN Identity for a vending machine. The machine owns its broker account and database device record; a board is associated with it after installation/activation.
DB device ID Database UUID for the vending-machine record. A candidate stable MQTT Client ID; it is not yet an adopted decision.
Dynamic Security client Broker-side authentication record, normally identified by username and holding password, optional Client ID binding, and role/group memberships.
Role Reusable collection of topic ACL rules. It does not itself authenticate a device or bind a Client ID.

The existing schema has separate fn_plate and sn_plate fields and a UUID primary key in schema_v1.sql (backend repo). Current repository flow documents activation messages using device/<PLATE>/... and calls both {serial} and {plate} device identifiers in places (activation flow (backend repo), MQTT protocol contract (backend repo)). Therefore the design must explicitly decide which lifecycle event assigns SN and when the MQTT identity/ACL transition occurs. The examples below are proposals, not a claim about current runtime behavior.

Decisions and recommendations by step

Step Decision / recommendation Status
1. Before a vending machine is assigned Treat the board as an FN identity. Give each board its own bootstrap credential and a small bootstrap ACL set. Do not give it vending-operation topics yet. Agreed direction; exact bootstrap topic contract remains open.
2. Bootstrap Client ID binding Dynamic Security allows the broker client record's Client ID binding to be unset. In that case, any Client ID can connect with that username/password. This is allowed for bootstrap if chosen, but it weakens device-instance binding. Prefer binding to a stable board identifier (ideally a backend-managed board UUID, or FN if that is stable and never reassigned) when available. Optional behavior confirmed; project choice open.
3. On MQTT CONNECT MQTT CONNECT always includes a Client ID. Username and password are optional in MQTT generally, but this authenticated device design should send the board/vending username and its password. “No Client ID binding in Dynamic Security” means the broker does not require one specific ID; it does not mean the CONNECT packet omits Client ID. Agreed protocol interpretation.
4. Bootstrap password Do not create a named Dynamic Security client with an unset password: that client cannot connect. Anonymous access is a separate mode, and all connections without usernames enter the shared unauthenticated group; this does not identify or isolate a board by FN. Use a unique per-board bootstrap secret. Agreed recommendation.
5. Assign SN / activate vending Provision an operational broker client for the vending identity, give it the vending role, and scope its topics to that SN. Candidate mapping: username = SN, password = unique secret, Client ID = DB device UUID. Recommended example; fields and exact cutover event need approval.
6. Replace a board or move it to another vending machine The operational account and ACL belong to the vending machine. Before handing the machine to replacement hardware, disable or rotate the existing secret so the old board cannot reconnect, then provision the replacement board. A board moved to another vending must not retain the previous vending's credentials. Ownership principle agreed; detailed handover procedure open.
7. Dynamic ACL setup Define reusable roles once (e.g. board-bootstrap, vending-device); create/update per-device clients and attach the proper role. Avoid manually copying a different ACL list for every vending machine. Use username %u substitution only where the username deliberately equals the identity embedded in the topic. Recommended model.
8. Default authorization Configure explicit deny defaults for publish-send, publish-receive, subscribe, and unsubscribe, then allow only reviewed topic/action pairs. Security recommendation; current broker config not verified.
9. Go backend and broker state The Go backend can call the Dynamic Security MQTT control API using its MQTT client, but broker state is not automatically a database transaction. Plan idempotent provisioning, recorded desired status, retries/reconciliation, and audit records before implementation. Design requirement; mechanism open.
10. Human/operator access Operators should use application endpoints with application RBAC and audit trails. Do not give routine operators the Dynamic Security administration credential. Separate the backend's broker-management principal from device identities and grant it only the required control API actions where feasible. Recommendation; operator model open.

MQTT connection fields

For a provisioned device using the proposed named-client model:

CONNECT {
  clientId:   <stable board or vending identifier>,
  username:   <FN during bootstrap, SN after vending assignment>,
  password:   <unique secret for that broker client>
}

If a Dynamic Security client has a configured Client ID, Mosquitto requires the username, password, and Client ID to match. If the Client ID binding is unset, the same username/password can connect using any Client ID. Avoid sharing credentials between boards or machines.

Use MQTT over TLS so credentials are protected in transit. Keep broker-management credentials separate from device credentials and protect the Dynamic Security control API.

Reusable role model

Dynamic Security separates client records from roles:

  1. Create a bootstrap role and an operational vending role with reviewed ACL rules.
  2. For each board, create one unique broker client (e.g. username derived from FN), set a random secret, choose whether to bind a board Client ID, and attach board-bootstrap.
  3. After the approved SN-assignment/activation transition, provision the vending client (e.g. username = SN, unique secret, Client ID = DB UUID) and attach vending-device.
  4. Retire or disable the bootstrap client's access according to the chosen post-activation lifecycle.
  5. During board replacement, revoke/rotate access before transferring the operational credential to replacement hardware.

An example role could use %u to make one ACL pattern cover every member while keeping each member scoped to their username-derived identity. For example, vending/%u/health only has the intended per-SN meaning if the operational username is that SN. If the stable Client ID is a DB UUID and topics contain SN, %c would point at the wrong value; use username=SN, per-client literal ACLs, or another carefully designed identity/topic mapping instead.

ACL event checklist

Review each topic independently in both directions. A device's ability to subscribe and its ability to receive broker-published messages are separate checks.

Direction Dynamic Security ACL event to review
Device publishes to broker publishClientSend
Device subscribes to a filter subscribeLiteral or subscribePattern
Broker message is delivered to device publishClientReceive
Device unsubscribes unsubscribeLiteral or unsubscribePattern

Build the actual allowlist from the maintained MQTT protocol contract, and annotate each topic with lifecycle phase, publisher, subscriber, topic identity (FN/SN), request/response behavior, and whether it contains sensitive data. Do not use a broad # permission for devices. Ensure request-response topics include the device's required subscription/receive rules, not just its publish rule.

Example (illustrative, not ready to apply)

This JSON illustrates a reusable vending role plus a single SN-specific client. The role shows only a small example subset; it is not the complete ACL for the repository's MQTT protocol. The exact Dynamic Security JSON/API schema should be checked against the deployed Mosquitto version before use.

{
  "defaultACLAccess": {
    "publishClientSend": false,
    "publishClientReceive": false,
    "subscribe": false,
    "unsubscribe": false
  },
  "roles": [
    {
      "rolename": "vending-device",
      "acls": [
        {
          "acltype": "publishClientSend",
          "topic": "vending/%u/health",
          "priority": 0,
          "allow": true
        },
        {
          "acltype": "publishClientSend",
          "topic": "vending/%u/sales/report",
          "priority": 0,
          "allow": true
        },
        {
          "acltype": "subscribePattern",
          "topic": "device/%u/activation/push",
          "priority": 0,
          "allow": true
        },
        {
          "acltype": "publishClientReceive",
          "topic": "device/%u/activation/push",
          "priority": 0,
          "allow": true
        }
      ]
    }
  ],
  "clients": [
    {
      "username": "SN000123",
      "password": "<unique-random-secret-from-secret-provisioning>",
      "clientid": "<device-database-uuid>",
      "roles": [
        {
          "rolename": "vending-device",
          "priority": -1
        }
      ]
    }
  ]
}

Do not commit real device passwords or broker-management credentials. In a real setup, secrets should be generated and provisioned through a protected secret-handling path, and plaintext should not be written to normal application logs.

Replacement and reassignment cases to design

Board replacement for the same vending machine

  1. Authorize the replacement in the backend/operator workflow.
  2. Disable the old broker client or rotate its password so existing and future use is denied.
  3. Associate the new FN with the existing vending-machine DB record.
  4. Provision the current vending credential to the replacement board using a controlled channel.
  5. Record old FN, new FN, vending DB ID/SN, actor, timestamps, and broker operation results.

Board moved to a different vending machine

  1. Revoke the old vending's credential from that board.
  2. Confirm it cannot publish/subscribe under the previous vending identity.
  3. Complete the new vending's ownership/activation checks.
  4. Provision only the new vending's broker identity and ACLs.

The device database is authoritative for ownership and lifecycle state. Broker state must be checked and reconciled with that state; a successful database write alone does not prove the broker accepted the ACL change, and a successful broker update alone does not prove the database transition committed.

Open decisions before implementation

  • What exact event completes SN assignment, and is it distinct from the current activation-code approval/ACK flow?
  • Which identity is used in each pre-SN topic: FN, current plate, DB UUID, or another value? The current docs use a generic plate in activation topics.
  • Should bootstrap clients be unbound to Client ID, bound to FN, or bound to a board UUID? How are FN/board identifiers protected against reuse?
  • Does each vending machine keep one operational credential through board replacement, or is each replacement given a newly rotated credential? Recommended: rotate on replacement.
  • How does the device securely receive credentials after SN assignment (factory channel, authenticated MQTT bootstrap response, physical/manual provisioning, or another mechanism)?
  • Which exact topic/action allowlist applies before and after SN assignment? Review all request-response topics and any legacy devices/... paths.
  • What broker-control operations must Go perform, and how are desired broker state, retries, timeouts, partial failures, and reconciliation persisted?
  • Which application roles may trigger activation, credential rotation, disablement, or board transfer, and what audit fields are mandatory?
  • How are credentials stored, delivered, rotated, and redacted in logs and support tooling?
  • Which deployed Mosquitto version and plugin configuration are authoritative for validation?

Codebase touchpoints inspected

  • device-onboarding-activation.md (backend repo) — current repository activation steps and message flow.
  • device-server-mqtt-protocol-fa.md (backend repo) — topic contract inventory and direction.
  • schema_v1.sql (backend repo) — device UUID, FN/SN, current identity, and activation fields.
  • internal/mqtt (backend repo) — current Go MQTT client package; its presence does not establish Dynamic Security integration.

Primary references