mosquitto: tighten ACLs (shellies + sml2mqtt least-privilege) (security review #sec-3/4) #126

Merged
k.renner merged 1 commits from feat/mqtt-acl-least-privilege into master 2026-07-08 06:47:17 +00:00
Owner

Security review — findings #sec-3 (MED) + #sec-4 (LOW). PR 2 of the series.

Per your question about per-device Shelly passwords: rejected as overkill — Mosquitto's static passwd/acl model can't scope per-device credentials without a dynamic-auth plugin (mosquitto-go-auth), which isn't worth it at this device count. The real risk is the shared user's ACL, fixed here.

Changes (in the ACL generated by the deployment shim)

  • shellies: readwrite homeassistant/# → write homeassistant/#. Shelly Gen2 firmwares self-publish HA MQTT-discovery configs, so the grant must stay — but a publish-only client never needs to read the entire discovery/state tree. Write-only keeps discovery working while removing device-inventory recon for a compromised Shelly. (readwrite shellies/# stays — devices publish state and subscribe to command topics.)
  • sml2mqtt: readwrite sml2mqtt/# → write sml2mqtt/#. Verified publish-only (its config has no subscribe/command topics — only LWT + value publishes).

Deliberately unchanged

  • open3e keeps readwrite open3e/# — it subscribes to open3e/cmnd (listener mode).
  • homeassistant/admin keep full access; evcc is already least-privilege.

kustomize build clean. Apply note: the broker is single-replica Recreate, so Flux rolling this = a brief MQTT reconnect (clients auto-reconnect). Post-merge watch: confirm Shelly entities still appear/update in HA (discovery write still works) and the power meter keeps publishing.

Next: #3 TLS cutover to :8883 — bigger and staged (edge-1 first), so I'll outline the sequence before touching live clients.

**Security review — findings #sec-3 (MED) + #sec-4 (LOW).** PR 2 of the series. Per your question about per-device Shelly passwords: **rejected as overkill** — Mosquitto's static `passwd`/`acl` model can't scope per-device credentials without a dynamic-auth plugin (mosquitto-go-auth), which isn't worth it at this device count. The real risk is the shared user's ACL, fixed here. ## Changes (in the ACL generated by the deployment shim) - **shellies: `readwrite homeassistant/#` → `write homeassistant/#`.** Shelly Gen2 firmwares self-publish HA MQTT-discovery configs, so the grant must stay — but a publish-only client never needs to *read* the entire discovery/state tree. Write-only keeps discovery working while removing device-inventory **recon** for a compromised Shelly. (`readwrite shellies/#` stays — devices publish state and subscribe to command topics.) - **sml2mqtt: `readwrite sml2mqtt/#` → `write sml2mqtt/#`.** Verified publish-only (its config has no subscribe/command topics — only LWT + value publishes). ## Deliberately unchanged - `open3e` keeps `readwrite open3e/#` — it subscribes to `open3e/cmnd` (listener mode). - `homeassistant`/`admin` keep full access; `evcc` is already least-privilege. `kustomize build` clean. **Apply note:** the broker is single-replica `Recreate`, so Flux rolling this = a brief MQTT reconnect (clients auto-reconnect). **Post-merge watch:** confirm Shelly entities still appear/update in HA (discovery write still works) and the power meter keeps publishing. Next: **#3 TLS cutover to :8883** — bigger and staged (edge-1 first), so I'll outline the sequence before touching live clients.
k.renner added 1 commit 2026-07-08 06:32:16 +00:00
Security review finding #sec-3/#sec-4.

- shellies: `readwrite homeassistant/#` -> `write homeassistant/#`. Shelly Gen2
  firmwares self-publish HA MQTT-discovery configs (so the grant must stay),
  but a publish-only client never needs to READ the whole discovery/state
  tree. Write-only removes device-inventory recon for a compromised Shelly
  while keeping discovery working. (`readwrite shellies/#` stays — devices
  publish state and subscribe to their command topics.)
- sml2mqtt: `readwrite sml2mqtt/#` -> `write sml2mqtt/#`. The bridge is
  publish-only (verified: its config has no subscribe/command topics, only
  LWT + value publishes), so it needs write only.

Left as-is deliberately: open3e keeps `readwrite open3e/#` (it subscribes to
open3e/cmnd in listener mode); homeassistant/admin keep full access; evcc is
already least-privilege. Per-device Shelly credentials were considered and
rejected — Mosquitto's static passwd/acl model can't scope them without a
dynamic-auth plugin, which isn't worth it at this device count.

Applying rolls the (single-replica, Recreate) broker -> a brief MQTT
reconnect. kustomize build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
k.renner merged commit b1c380c825 into master 2026-07-08 06:47:17 +00:00
Sign in to join this conversation.