218 lines
10 KiB
Markdown
218 lines
10 KiB
Markdown
# 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
|
|
|
|
```text
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
1. PostgreSQL starts; backend creates its schema (or run migrations
|
|
explicitly, see below) and seeds the management admin.
|
|
2. Mosquitto starts with `mosquitto/bootstrap/mosquitto.conf` on the
|
|
internal network; the dynsec plugin seeds its `admin` client from
|
|
`MOSQUITTO_DYNSEC_PASSWORD`.
|
|
3. Backend connects to dynsec as that admin client — the management
|
|
channel is alive.
|
|
4. 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/v1` API. No files involved.
|
|
* **Generated mosquitto.conf (rare)** — listeners/global settings are
|
|
generated from the DB model and deployed through `mosquitto-control`,
|
|
which OWNS the shared PID namespace (`pid: "service:mosquitto-control"`
|
|
on the broker); it signals the broker process and can restart it — and
|
|
survives broker restarts — without Docker sockets (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):
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
cd frontend && npm install && npm run dev # http://localhost:5173
|
|
```
|
|
|
|
## Repository layout
|
|
|
|
```text
|
|
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
|
|
|
|
- [x] Docker Compose stack (5 services, 2 networks, no Docker socket)
|
|
- [x] Mosquitto 2.1.2 (pinned) + PostgreSQL
|
|
- [x] FastAPI backend + React (Vite) frontend
|
|
- [x] 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)
|
|
- [x] Management RBAC (super_admin / broker_admin / operator / auditor)
|
|
- [x] Dynamic Security integration (backend as the dynsec admin client)
|
|
- [x] MQTT clients: create / disable / enable / delete, password reset,
|
|
random passwords (returned exactly once), expected client-ID
|
|
binding (anti-credential-sharing, spec section 11)
|
|
- [x] Groups: create / delete, member + role assignment with priorities
|
|
- [x] Roles: create / delete, ACL editor (all six ACL types, `%u`/`%c`
|
|
substitution, priorities, draft-then-save)
|
|
- [x] Broker configuration editor, basic mode (structured forms +
|
|
read-only preview of the generated `mosquitto.conf`)
|
|
- [x] Listener management (port, bind address, protocol, anonymous,
|
|
max-connections; enable/disable, edit, delete)
|
|
- [x] Configuration validation (`mosquitto --test-config` via sidecar)
|
|
- [x] Configuration generation from the PostgreSQL model
|
|
|
|
Hardening done beyond the plan:
|
|
|
|
- [x] Management identity protection: the dynsec `admin` client is hidden
|
|
from Clients and refuses reset/disable/delete (resetting it kicks
|
|
every connection using it — including the backend's own control
|
|
channel)
|
|
- [x] Dynsec channel resilience: disconnects are detected immediately and
|
|
the MQTT client is rebuilt instead of failing silently
|
|
- [x] Login flow returns to the originally requested page; mid-session
|
|
expiry redirects to login with a return target
|
|
- [x] 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
|
|
|
|
- [x] Configuration revisions (immutable history; list + detail API and UI)
|
|
- [x] Configuration deployment with health check (dynsec roundtrip probe)
|
|
- [x] Broker reload (`mosquitto_signal config-reload` via sidecar)
|
|
- [x] mosquitto-control service (validate / atomic activate / signal /
|
|
broker health)
|
|
- [x] CA creation — internal CA (RSA 3072/4096, ECDSA P-256/P-384),
|
|
private keys encrypted at rest (AES-256-GCM)
|
|
- [x] Server certificate generation (CN, DNS/IP SANs, serverAuth EKU,
|
|
signed by a platform CA; PEM download)
|
|
- [x] Client certificates (CN = MQTT username, clientAuth EKU,
|
|
one-time bundle with cert + key + CA chain as file downloads;
|
|
identity binding to the client metadata)
|
|
- [x] 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)
|
|
- [x] Certificate inventory (serial, fingerprint, SANs, status, expiry)
|
|
- [x] 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
|
|
- [x] 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)
|
|
- [x] Restart-required detection + restart from the UI: deploy compares
|
|
listener lines against the previous deployed revision (reload
|
|
cannot apply listener changes, §28) and sets a pending-restart
|
|
flag; a glowing sidebar indicator links to Administration →
|
|
System, where the broker restarts via the control sidecar
|
|
(SIGTERM to PID 1, Docker policy revives it; audited as
|
|
BROKER_RESTARTED, §29/§38)
|
|
- [ ] Configuration diff view (revision N-1 → N)
|
|
- [ ] Configuration rollback (creates a new revision, §62)
|
|
- [ ] 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
|
|
|
|
- [x] Audit logging (backend records every privileged action: LOGIN,
|
|
LOGOUT, CLIENT_*, GROUP_*, ROLE_*, ACL_CHANGED, LISTENER_*,
|
|
BROKER_CONFIG_DEPLOYED, BROKER_RESTARTED, USER_*, …)
|
|
- [x] Client metadata/tags (owner / purpose / tags in PostgreSQL; API
|
|
accepts them on create)
|
|
- [x] Administration → System page: broker status + restart (the
|
|
§28 restart gap is closed end-to-end from the UI)
|
|
- [x] Broker Status dashboard (§33/§55): $SYS telemetry collected via
|
|
the existing dynsec MQTT connection (extra $SYS/# subscription +
|
|
sys-observe role grant at startup), exposed at
|
|
GET /broker/status and shown on Broker → Status (version,
|
|
uptime, connected/stored clients, subscriptions, retained
|
|
messages, message/publish counters, 1-min load averages, bytes)
|
|
- [ ] 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_password` yet)
|
|
- [ ] Client metadata editing in the UI
|
|
- [ ] ACL simulator (§15)
|
|
- [ ] 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) — PARTIAL
|
|
|
|
- [x] Topic explorer (spec §35/§36): live message stream over SSE
|
|
(one MQTT v5 client per browser session, SUBACK-gated subscribe),
|
|
publish with QoS/retain (audited as TOPIC_PUBLISHED — metadata
|
|
only, never the payload), retained-message listing and clearing
|
|
(audited as RETAINED_CLEARED); RBAC: viewing = operator+,
|
|
publishing/clearing = broker-admin+ — verified live end-to-end
|
|
- [ ] Topic explorer: consolidated topic view (spec §35.1) — one row
|
|
per known topic; highlight differences between the new and the
|
|
previous message per topic (payload, QoS, retain, MQTT 5
|
|
properties); briefly glow the row on new-message arrival;
|
|
browse the known history of each topic
|
|
- [ ] Connection tester + client provisioning wizard
|
|
- [ ] Bridge configuration
|
|
- [ ] Retained-message administration (partially covered by the topic
|
|
explorer: listing + clearing; a dedicated §36 page can follow)
|
|
- [ ] Certificate expiry alerts
|
|
- [ ] Prometheus metrics + external notifications
|
|
- [ ] Change approval (four-eyes) workflow
|
|
- [ ] Topic namespace / JSON-schema registries, and later Phase-3 items |