edge-host: TLS for edge MQTT via a local bridging mosquitto (security review #sec-1, edge-1 leg) #127

Merged
k.renner merged 1 commits from feat/edge-mqtt-tls-bridge into master 2026-07-08 07:02:45 +00:00
Owner

Security review — #sec-1 (HIGH), edge-1 leg. PR 3 of the series. You picked the local-bridge approach; this implements it, self-contained to the edge-host role (no cluster change).

Why a bridge (not just port 8883)

Verified: open3e (0.6.2) and e3oncan have no TLS support — their MQTT code does a bare mqtt_client.connect(host, port), no tls_set(), no cert flags. Only sml2mqtt could do native TLS. So edge-1 can't just point at :8883.

What this does

A local mosquitto on edge-1 that open3e/e3oncan/sml2mqtt reach over loopback (127.0.0.1:1883), bridged to the cluster broker over TLS (mqtt.renner.uno:8883; public LE cert validated via the system CA bundle). Plaintext never leaves the host; the network hop is encrypted.

  • Two bridge connections preserve the existing cluster users — one as open3e (open3e/# out + open3e/cmnd back), one as sml2mqtt (sml2mqtt/# out). So no cluster-side change, and the current ACLs already cover it.
  • Availability bonus (#avail-4): the local broker buffers telemetry in RAM during cluster/network blips and flushes on reconnect.

eMMC (per your note)

The local broker writes nothing to flash: persistence false (queue in RAM) + log_dest stdout → the volatile journal → Loki. Called out in the config + README so it stays that way.

Verified before building

edge-1 → :8883 TLS validates (Verify return code: 0); mosquitto 2.1.2 in Arch extra; existing open3e/sml2mqtt cluster ACLs suffice for the bridge; #125's netpol already allows :8883. ansible-playbook --syntax-check clean.

Apply / rollback

Applying installs mosquitto and restarts open3e/e3oncan/sml2mqtt → a brief telemetry reconnect (open3e = heat pump, so a short blip). After it, edge-1's MQTT on the IoT VLAN is TLS-only. This does NOT yet remove :1883 from the cluster LB — that's the final step of #sec-1 after evcc/HA are also on TLS. Rollback = point edge_mqtt_host back to mqtt.renner.uno, re-run.

Want me to apply + validate on edge-1 on your go (confirm both bridges connect, topics still land in the cluster, and edge-1 no longer opens plaintext :1883)?

**Security review — #sec-1 (HIGH), edge-1 leg.** PR 3 of the series. You picked the local-bridge approach; this implements it, self-contained to the edge-host role (no cluster change). ## Why a bridge (not just port 8883) Verified: **open3e (0.6.2) and e3oncan have no TLS support** — their MQTT code does a bare `mqtt_client.connect(host, port)`, no `tls_set()`, no cert flags. Only sml2mqtt could do native TLS. So edge-1 can't just point at :8883. ## What this does A **local mosquitto on edge-1** that open3e/e3oncan/sml2mqtt reach over **loopback** (`127.0.0.1:1883`), **bridged to the cluster broker over TLS** (`mqtt.renner.uno:8883`; public LE cert validated via the system CA bundle). Plaintext never leaves the host; the network hop is encrypted. - **Two bridge `connection`s preserve the existing cluster users** — one as `open3e` (`open3e/#` out + `open3e/cmnd` back), one as `sml2mqtt` (`sml2mqtt/#` out). So **no cluster-side change**, and the current ACLs already cover it. - **Availability bonus (#avail-4):** the local broker buffers telemetry in RAM during cluster/network blips and flushes on reconnect. ## eMMC (per your note) The local broker writes **nothing** to flash: `persistence false` (queue in RAM) + `log_dest stdout` → the volatile journal → Loki. Called out in the config + README so it stays that way. ## Verified before building edge-1 → :8883 TLS validates (`Verify return code: 0`); `mosquitto 2.1.2` in Arch `extra`; existing open3e/sml2mqtt cluster ACLs suffice for the bridge; #125's netpol already allows :8883. `ansible-playbook --syntax-check` clean. ## Apply / rollback Applying installs mosquitto and restarts open3e/e3oncan/sml2mqtt → a brief telemetry reconnect (open3e = heat pump, so a short blip). After it, edge-1's MQTT on the IoT VLAN is TLS-only. **This does NOT yet remove :1883 from the cluster LB** — that's the final step of #sec-1 after evcc/HA are also on TLS. Rollback = point `edge_mqtt_host` back to `mqtt.renner.uno`, re-run. Want me to apply + validate on edge-1 on your go (confirm both bridges connect, topics still land in the cluster, and edge-1 no longer opens plaintext :1883)?
k.renner added 1 commit 2026-07-08 07:02:11 +00:00
Security review #sec-1 (edge-1 leg). open3e/e3oncan/sml2mqtt publish to the
cluster broker in plaintext over the untrusted IoT VLAN, and open3e/e3oncan
have no native MQTTS (their MQTT code does a bare connect()), so "just use
8883" isn't possible for them.

Run a local mosquitto on edge-1 that the clients reach over loopback
(127.0.0.1:1883), bridged to the cluster broker over TLS (mqtt.renner.uno:8883,
public LE cert validated via the system CA bundle). Plaintext stays on
loopback; the wire hop is encrypted. Two bridge connections preserve the
existing cluster identities (open3e user carries open3e/# out + open3e/cmnd
back; sml2mqtt user carries sml2mqtt/# out), so NO cluster-side change is
needed and the existing ACLs already suffice.

Bonus (availability, #avail-4): the local broker buffers telemetry in RAM and
flushes on reconnect, so edge-1 no longer hard-depends on cluster/network
uptime for every publish.

eMMC: the local broker writes NOTHING to flash — persistence false (queue in
RAM) + log_dest stdout (volatile journal -> Loki).

Verified before building: edge-1 -> :8883 TLS validates (return code 0);
mosquitto 2.1.2 in Arch extra; existing open3e/sml2mqtt cluster ACLs cover the
bridge; #125 netpol already allows :8883.

Rollback: point edge_mqtt_host back to mqtt.renner.uno and re-run.

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