matter-server: bind the :5580 control WebSocket to the pod address #144

Merged
k.renner merged 1 commits from fix/matter-server-bind-eth0 into master 2026-07-10 14:33:33 +00:00
Owner

The hole (same class as #142)

matter-server is multi-homed — net1 is a macvlan on the IoT VLAN for Matter commissioning — and it binds its :5580 control WebSocket to 0.0.0.0. macvlan traffic bypasses Cilium policy, so the unauthenticated Matter control API was reachable from the whole IoT VLAN.

Verified today from edge-1 (on v30iot):

$ curl http://10.2.30.117:5580/
200

An unauthenticated IoT client could drive the Matter fabric over that socket — commission/remove/control devices, read fabric state. This is the sibling exposure flagged (report-only) when shelly-manager was fixed; you asked to close it too.

The fix

python-matter-server has a dedicated --listen-address flag, independent of --primary-interface. So the control socket and the Matter fabric interface are separable in the app's own CLI:

args:
  - --listen-address=$(POD_IP)   # WebSocket → eth0 only
  - --primary-interface=net1     # Matter commissioning → IoT VLAN (unchanged)
env:
  - name: POD_IP
    valueFrom: { fieldRef: { fieldPath: status.podIP } }

:5580 is then served only on the cluster pod CIDR. HA reaches it via the IPv4 ClusterIP Service (ws://matter-server.home-assistant.svc:5580/ws), which targets the pod IP.

Why nothing breaks

  • HA control path: ClusterIP (IPv4-only, 10.99.52.180) → podIP:5580. Intact.
  • Matter commissioning/discovery: unchanged — --primary-interface=net1 still pins link-local Matter ops to the IoT VLAN. --listen-address only moves the WebSocket bind; it does not touch the Matter SDK's interface selection.
  • No probes to reconcile: matter-server has none (a separate, deferred audit finding).

Confirmed before this change: current listeners are 0.0.0.0:5580 + [::]:5580; Service is single-stack IPv4, so binding the IPv4 pod IP loses no consumer.

Verified so far

kubectl kustomize builds; rendered args carry --listen-address=$(POD_IP) and the POD_IP downward-API env.

Post-merge checks (I'll run before calling it done)

  1. curl http://<net1 ip>:5580/ from edge-1 now fails.
  2. :5580 listens on the pod IP only (not 0.0.0.0).
  3. HA's Matter integration reconnects; all 13 nodes re-subscribe.
  4. Commissioning path (net1) still intact.

Still deferred (not this PR)

otbr:8081 has the same exposure and is next (via REST_HOST=$(POD_IP)), one at a time. The in-cluster CNP bypass and the no-probe gap remain report-only per your triage.

## The hole (same class as #142) matter-server is multi-homed — `net1` is a macvlan on the **IoT VLAN** for Matter commissioning — and it binds its `:5580` control WebSocket to `0.0.0.0`. macvlan traffic bypasses Cilium policy, so the unauthenticated Matter control API was reachable from the whole IoT VLAN. Verified today from `edge-1` (on v30iot): ``` $ curl http://10.2.30.117:5580/ 200 ``` An unauthenticated IoT client could drive the Matter fabric over that socket — commission/remove/control devices, read fabric state. This is the sibling exposure flagged (report-only) when shelly-manager was fixed; you asked to close it too. ## The fix `python-matter-server` has a dedicated `--listen-address` flag, **independent** of `--primary-interface`. So the control socket and the Matter fabric interface are separable in the app's own CLI: ```yaml args: - --listen-address=$(POD_IP) # WebSocket → eth0 only - --primary-interface=net1 # Matter commissioning → IoT VLAN (unchanged) env: - name: POD_IP valueFrom: { fieldRef: { fieldPath: status.podIP } } ``` `:5580` is then served only on the cluster pod CIDR. HA reaches it via the IPv4 ClusterIP Service (`ws://matter-server.home-assistant.svc:5580/ws`), which targets the pod IP. ## Why nothing breaks - **HA control path:** ClusterIP (IPv4-only, `10.99.52.180`) → podIP:5580. Intact. - **Matter commissioning/discovery:** unchanged — `--primary-interface=net1` still pins link-local Matter ops to the IoT VLAN. `--listen-address` only moves the *WebSocket* bind; it does not touch the Matter SDK's interface selection. - **No probes to reconcile:** matter-server has none (a separate, deferred audit finding). Confirmed before this change: current listeners are `0.0.0.0:5580` + `[::]:5580`; Service is single-stack IPv4, so binding the IPv4 pod IP loses no consumer. ## Verified so far `kubectl kustomize` builds; rendered args carry `--listen-address=$(POD_IP)` and the `POD_IP` downward-API env. ## Post-merge checks (I'll run before calling it done) 1. `curl http://<net1 ip>:5580/` from `edge-1` now **fails**. 2. `:5580` listens on the pod IP only (not `0.0.0.0`). 3. HA's Matter integration reconnects; all 13 nodes re-subscribe. 4. Commissioning path (net1) still intact. ## Still deferred (not this PR) otbr:8081 has the same exposure and is next (via `REST_HOST=$(POD_IP)`), one at a time. The in-cluster CNP bypass and the no-probe gap remain report-only per your triage.
k.renner added 1 commit 2026-07-10 13:37:00 +00:00
Same class as the shelly-manager bypass (#142). matter-server is
multi-homed — net1 is a macvlan on the IoT VLAN for Matter commissioning —
and it binds the :5580 control WebSocket to 0.0.0.0. macvlan traffic
bypasses Cilium policy, so the unauthenticated Matter control API was
reachable from the whole IoT VLAN.

Verified 2026-07-10 from edge-1 (on v30iot):

  GET http://10.2.30.117:5580/  ->  200

An unauthenticated IoT client could drive the Matter fabric (commission /
remove / control devices, read fabric state) over that socket.

python-matter-server has a dedicated `--listen-address` flag, independent
of `--primary-interface`. Bind the WebSocket to the pod eth0 address
(--listen-address=$(POD_IP), POD_IP=status.podIP) so :5580 is served only
on the cluster pod CIDR. `--primary-interface=net1` is unchanged, so Matter
commissioning/discovery still uses the IoT VLAN. HA reaches the WebSocket
via the IPv4 ClusterIP Service (ws://matter-server...svc:5580/ws), which
targets the pod IP.

The Service is single-stack IPv4, so binding the IPv4 pod IP loses no
consumer. matter-server has no probes, so there is no probe path to
reconcile (that probe gap is a separate, deferred audit finding).

Verified: kubectl kustomize builds; rendered args carry
--listen-address=$(POD_IP) and the POD_IP downward-API env. Post-merge
checks to run: GET net1:5580 from the IoT VLAN now fails; :5580 listens on
the pod IP only; HA's Matter integration reconnects and all 13 nodes
re-subscribe; commissioning still works (net1 unchanged).

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