9.0 KiB
MQTT Management Platform — Release 1
Web-based administration for an Eclipse Mosquitto 2.1 broker: management login/RBAC, MQTT client/group/role management with ACL editing via the Dynamic Security plugin, and generated + validated broker configuration.
Architecture and scope follow specification.md (Release 1, §68.1).
Stack
mqtt-frontend React (Vite build) served by nginx, proxies /api → backend
mqtt-backend FastAPI: management auth, RBAC, dynsec client, config engine
mosquitto Eclipse Mosquitto 2.1.2 (pinned), dynsec enabled
mosquitto-control control sidecar: validate/activate/reload configs
mqtt-db PostgreSQL (management data only; NOT host-exposed)
Networks: frontend-net (frontend↔backend) and backend-net
(backend↔db↔mosquitto↔control). Only the frontend HTTP port is published.
Quick start
cp .env.example .env # then EDIT the change-me values!
docker compose up -d --build
Open http://localhost:8080 and sign in with ADMIN_INITIAL_USERNAME /
ADMIN_INITIAL_PASSWORD from your .env.
Bootstrap sequence on first start:
- PostgreSQL starts; backend creates its schema (or run migrations explicitly, see below) and seeds the management admin.
- Mosquitto starts with
mosquitto/bootstrap/mosquitto.confon the internal network; the dynsec plugin seeds itsadminclient fromMOSQUITTO_DYNSEC_PASSWORD. - Backend connects to dynsec as that admin client — the management channel is alive.
- Configuration changes made in the UI are generated from the
PostgreSQL model, validated with
mosquitto --test-config(via the control sidecar), deployed atomically, and recorded as a revision.
Note for Windows users: the compose healthcheck for mosquitto and the control sidecar rely on Unix facilities (
pid: service:, POSIX shell commands); run the stack with Docker Desktop + WSL2 as usual — nothing here assumes a Linux host beyond Docker itself.
Configuration channels (why this design)
- Dynamic Security (runtime) — clients/groups/roles/ACLs change via
the MQTT
$CONTROL/dynamic-security/v1API. No files involved. - Generated mosquitto.conf (rare) — listeners/global settings are
generated from the DB model and deployed through
mosquitto-control, which shares the broker's PID namespace (pid: "service:mosquitto") to signal reloads. No Docker socket is mounted anywhere (spec §29). - Mosquitto 2.1's HTTP API is not used as a management channel: it has no authentication and (currently) exposes read-only plugin info.
Development
Backend (in container):
docker compose exec mqtt-backend pytest -q # API + generator tests
docker compose exec mqtt-backend ruff check app tests
docker compose exec mqtt-backend alembic upgrade head # explicit migrations
Frontend (local dev server with proxy to backend):
cd frontend && npm install && npm run dev # http://localhost:5173
Repository layout
compose.yaml stack definition (5 services, 2 networks)
.env.example environment template (secrets: edit before use)
mosquitto/bootstrap/ the one file that ships with the deployment
broker-control/ validate / atomic-activate / reload sidecar
backend/ FastAPI app, models, dynsec client, configgen
frontend/ React + Vite SPA served by nginx
Status & TODO — per release plan (specification.md §68)
Progress against the MVP release plan. Checked items are implemented and covered by the test suite / running stack.
Release 1 — Core broker administration (§68.1) — COMPLETE
- Docker Compose stack (5 services, 2 networks, no Docker socket)
- Mosquitto 2.1.2 (pinned) + PostgreSQL
- FastAPI backend + React (Vite) frontend
- Management authentication (HttpOnly session cookies, Argon2id,
ADMIN_INITIAL_*bootstrap, must-change-password flagging; the backend exposes the change-password API — UI enforcement of the forced change is still pending, see Release 3) - Management RBAC (super_admin / broker_admin / operator / auditor)
- Dynamic Security integration (backend as the dynsec admin client)
- MQTT clients: create / disable / enable / delete, password reset, random passwords (returned exactly once), expected client-ID binding (anti-credential-sharing, spec section 11)
- Groups: create / delete, member + role assignment with priorities
- Roles: create / delete, ACL editor (all six ACL types,
%u/%csubstitution, priorities, draft-then-save) - Broker configuration editor, basic mode (structured forms +
read-only preview of the generated
mosquitto.conf) - Listener management (port, bind address, protocol, anonymous, max-connections; enable/disable, edit, delete)
- Configuration validation (
mosquitto --test-configvia sidecar) - Configuration generation from the PostgreSQL model
Hardening done beyond the plan:
- Management identity protection: the dynsec
adminclient is hidden from Clients and refuses reset/disable/delete (resetting it kicks every connection using it — including the backend's own control channel) - Dynsec channel resilience: disconnects are detected immediately and the MQTT client is rebuilt instead of failing silently
- Login flow returns to the originally requested page; mid-session expiry redirects to login with a return target
- Navigation follows spec §57; unimplemented pages are greyed out with the planned release as tooltip
Release 2 — PKI, versioning, and controlled change (§68.2) — PARTIAL
- Configuration revisions (immutable history; list + detail API and UI)
- Configuration deployment with health check (dynsec roundtrip probe)
- Broker reload (
mosquitto_signal config-reloadvia sidecar) - mosquitto-control service (validate / atomic activate / signal / broker health)
- CA creation — internal CA (RSA 3072/4096, ECDSA P-256/P-384), private keys encrypted at rest (AES-256-GCM)
- Server certificate generation (CN, DNS/IP SANs, serverAuth EKU, signed by a platform CA; PEM download)
- Client certificates (CN = MQTT username, clientAuth EKU, one-time bundle with cert + key + CA chain as file downloads; identity binding to the client metadata)
- mTLS listeners end-to-end: TLS section in the listener form (certificate picker, min TLS version, require_certificate, use_identity_as_username) — verified live with a client-cert publish through an mTLS listener (spec section 24)
- Certificate inventory (serial, fingerprint, SANs, status, expiry)
- TLS config generation: listeners with a bound certificate emit certfile/keyfile/cafile; cert files are published to a shared certs volume (uid 1883, key mode 0640) before validation/deploy
- Unpublished-port warning: listeners on ports not mapped by compose.yaml warn in the preview/deploy (reachable only inside the Docker network; keep PUBLISHED_PORTS in sync with compose)
- Configuration diff view (revision N-1 → N)
- Configuration rollback (creates a new revision, §62)
- Reload-vs-restart classification (§28) — the deploy health check passes on reload, but a NEW listener still needs an explicit broker restart (docker compose restart mosquitto); surfacing that in the UI is pending
- CA import (existing external CA)
- Root-key export + removal workflow (§18)
- CSR signing (client-side key generation)
- Certificate revocation + CRL generation/publishing
- Client-cert disable: revoking access for a connected device (needs CRL or dynsec disable + kick; the interim path — disabling the client — already refuses reconnects even with a valid cert)
Release 3 — Operations, safety, and visibility (§68.3) — PARTIAL
- Audit logging (backend records every privileged action: LOGIN, LOGOUT, CLIENT_, GROUP_, ROLE_, ACL_CHANGED, LISTENER_, BROKER_CONFIG_DEPLOYED, USER_*, …)
- Client metadata/tags (owner / purpose / tags in PostgreSQL; API accepts them on create)
- Audit browsing UI (events are recorded; no viewer page yet)
- Forced password-change UI (backend flag + API exist; the frontend
does not enforce
must_change_passwordyet) - Client metadata editing in the UI
- ACL simulator (§15)
- Dashboard ($SYS monitoring, broker status, §33)
- Certificate expiry monitoring
- Backup and restore (logical, encrypted, §45)
- Configuration drift detection (§48)
- Last-known-connection tracking + connected-client view (§34)
Post-MVP (§68.4 / §69 / §70) — NOT STARTED
- Topic explorer / MQTT test client
- Connection tester + client provisioning wizard
- Bridge configuration
- Retained-message administration
- Certificate expiry alerts
- Prometheus metrics + external notifications
- Change approval (four-eyes) workflow
- Topic namespace / JSON-schema registries, and later Phase-3 items