diff --git a/kubernetes/apps/home-assistant/zigbee2mqtt-config.yaml b/kubernetes/apps/home-assistant/zigbee2mqtt-config.yaml index 9d86d122..3ed591e5 100644 --- a/kubernetes/apps/home-assistant/zigbee2mqtt-config.yaml +++ b/kubernetes/apps/home-assistant/zigbee2mqtt-config.yaml @@ -11,11 +11,11 @@ data: # config once the PVC has been seeded — change the on-PVC file (via the Z2M # frontend or an edit) instead. Kept here so a fresh PVC comes up configured. # - # Secrets are NOT in this file: both the MQTT password and the Zigbee - # network_key are quoted parseValueRefs (`"!secret "`) resolved from - # /app/data/secret.yaml, which the init container renders — the MQTT password - # from the broker-credential Secret, and the network_key extracted from the - # OpenBao coordinator backup. The non-secret network params (channel/pan_id/ + # Secrets are NOT in this file: the Zigbee network_key is a quoted + # parseValueRef (`"!secret "`) resolved from /app/data/secret.yaml, + # which the init container renders from the OpenBao coordinator backup. MQTT + # uses the client certificate mounted at /mqtt-tls (REN-953); the Deployment + # also sets the mqtt server/ca/cert/key by env, which wins over this file. The non-secret network params (channel/pan_id/ # ext_pan_id) are in `advanced` below. configuration.yaml: | # Home Assistant MQTT discovery — Z2M publishes device configs under the @@ -32,14 +32,12 @@ data: mqtt: base_topic: zigbee2mqtt - server: mqtt://mosquitto:1883 - user: zigbee2mqtt - # MUST be a QUOTED string: Z2M reads secrets via parseValueRef on the - # string value (regex `! `), NOT a YAML `!secret` tag. Unquoted - # `!secret ...` is parsed as a tag and throws "unknown scalar tag - # !". The init container renders /app/data/secret.yaml with - # `mqtt_password` from the broker-credential Secret. - password: "!secret mqtt_password" + # Mutual TLS: the broker takes the username from the certificate CN + # (zigbee2mqtt-bridge), so no user or password here. + server: mqtts://mosquitto:8884 + ca: /mqtt-tls/ca.crt + cert: /mqtt-tls/tls.crt + key: /mqtt-tls/tls.key # SMLIGHT SLZB-06 (Texas Instruments CC2652, zigpy-znp under ZHA) reached # over TCP on the IoT VLAN — the SAME coordinator ZHA uses today diff --git a/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml b/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml index 3422b915..0ffa9025 100644 --- a/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml +++ b/kubernetes/apps/home-assistant/zigbee2mqtt-deployment.yaml @@ -32,9 +32,11 @@ spec: # backup also handles the ZHA<->Z2M ext_pan_id byte-order reversal — # which is why we restore a backup rather than hand-set advanced.ext_pan_id). # Renders /app/data/secret.yaml (referenced by the config as quoted - # "!secret" parseValueRefs) with the MQTT password AND the Zigbee - # network_key, the latter extracted from the OpenBao coordinator backup - # as a natural-byte-order int array. + # "!secret" parseValueRefs) with the Zigbee network_key, extracted from + # the OpenBao coordinator backup as a natural-byte-order int array. No + # MQTT password since REN-953: `mqtt_password` is absent, so Z2M's + # "!secret mqtt_password" resolves to nothing and it logs in by + # certificate alone. - name: seed-data # Our own alpine + jq image, built by build-ci-images.yml. DATED tag, # never :latest — rebuilding this base for an unrelated consumer @@ -114,7 +116,6 @@ spec: # hex key "fe0b..2d" -> "0xfe,0x0b,..,0x2d" (natural order) NK=$(printf '%s' "$KEY" | sed 's/../0x&,/g; s/,$//') cat > /app/data/secret.yaml </dev/null || true } @@ -152,7 +155,7 @@ while true; do log "confirmed — requesting a Zigbee2MQTT restart to re-run discovery" if timeout "$PUB_TIMEOUT" mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" \ - -u "$MQTT_USER" -P "$MQTT_PASSWORD" \ + --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" \ -t "$BASE_TOPIC/bridge/request/restart" -m '' 2>/dev/null; then date -u +%s > "$STAMP" log "restart requested" diff --git a/kubernetes/apps/home-assistant/zigbee2mqtt-interview-watch.sh b/kubernetes/apps/home-assistant/zigbee2mqtt-interview-watch.sh index d7f0580e..45e90100 100644 --- a/kubernetes/apps/home-assistant/zigbee2mqtt-interview-watch.sh +++ b/kubernetes/apps/home-assistant/zigbee2mqtt-interview-watch.sh @@ -53,7 +53,10 @@ set -u DATA=/app/data DB="$DATA/database.db" MQTT_HOST=${MQTT_HOST:-mosquitto} -MQTT_USER=${MQTT_USER:-zigbee2mqtt} +MQTT_PORT=${MQTT_PORT:-8884} +# ca.crt, tls.crt, tls.key of the pod's client certificate (register account +# zigbee2mqtt-bridge, REN-953). The broker takes the username from its CN. +MQTT_TLS_DIR=${MQTT_TLS_DIR:-/mqtt-tls} # Must track mqtt.base_topic in zigbee2mqtt-config.yaml. Overridable via env so # the two can be kept in step from the Deployment rather than by editing this # script; the MQTT ACL for this user is scoped to `zigbee2mqtt/#`, so a change @@ -219,10 +222,6 @@ while :; do fi log "frame from $ieee (nwk=$nwk cluster=$cluster) while awake -> requesting interview" - # NOTE: mosquitto_pub can only take a password via argv (-P, or an -L URL); - # it has no --pw-file or env option, so this cannot be avoided with this - # client. Not a cross-container exposure: the pod does not set - # shareProcessNamespace, so /proc is not shared with the Z2M container. # Bounded with `timeout`, which also covers a TCP connect that hangs rather # than being refused. The read loop is single-threaded, so an unbounded # publish would stall it and miss other devices' wake windows. @@ -230,7 +229,8 @@ while :; do # NOT `mosquitto_pub -W`: that flag does not exist for mosquitto_pub (it is # a mosquitto_sub option), and passing it makes every publish fail on a # usage error — verified against the client in this image. - if timeout "$PUB_TIMEOUT" mosquitto_pub -h "$MQTT_HOST" -u "$MQTT_USER" -P "$MQTT_PASSWORD" \ + if timeout "$PUB_TIMEOUT" mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" \ + --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" \ -t "$BASE_TOPIC/bridge/request/device/interview" \ -m "{\"id\":\"$ieee\"}" 2>/dev/null; then # Stamp only on a SUCCESSFUL publish. Stamping unconditionally would burn diff --git a/kubernetes/apps/home-assistant/zigbee2mqtt-ws90-reporting.md b/kubernetes/apps/home-assistant/zigbee2mqtt-ws90-reporting.md index 973952e4..73aad2c8 100644 --- a/kubernetes/apps/home-assistant/zigbee2mqtt-ws90-reporting.md +++ b/kubernetes/apps/home-assistant/zigbee2mqtt-ws90-reporting.md @@ -103,25 +103,25 @@ POD=$(kubectl get pod -n home-assistant -l app.kubernetes.io/name=zigbee2mqtt -o ``` ```sh -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msTemperatureMeasurement\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":10}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msTemperatureMeasurement\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":10}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msRelativeHumidity\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":50}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msRelativeHumidity\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":50}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msPressureMeasurement\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msPressureMeasurement\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msIlluminanceMeasurement\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":5}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"msIlluminanceMeasurement\",\"attribute\":\"measuredValue\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":5}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Wind\",\"attribute\":\"windSpeed\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Wind\",\"attribute\":\"windSpeed\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Wind\",\"attribute\":\"windDirection\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Wind\",\"attribute\":\"windDirection\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Wind\",\"attribute\":\"gustSpeed\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Wind\",\"attribute\":\"gustSpeed\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90UV\",\"attribute\":\"uvIndex\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90UV\",\"attribute\":\"uvIndex\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Rain\",\"attribute\":\"rainStatus\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Rain\",\"attribute\":\"rainStatus\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' -kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p 1883 -u "$MQTT_USER" -P "$MQTT_PASSWORD" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Rain\",\"attribute\":\"precipitation\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' +kubectl exec -n home-assistant "$POD" -c discovery-guard -- sh -c 'mosquitto_pub -h "$MQTT_HOST" -p "$MQTT_PORT" --cafile "$MQTT_TLS_DIR/ca.crt" --cert "$MQTT_TLS_DIR/tls.crt" --key "$MQTT_TLS_DIR/tls.key" -t "$BASE_TOPIC/bridge/request/device/configure_reporting" -m "{\"id\":\"0xc02cedfffe958f40\",\"endpoint\":1,\"cluster\":\"shellyWS90Rain\",\"attribute\":\"precipitation\",\"minimum_report_interval\":5,\"maximum_report_interval\":3600,\"reportable_change\":1,\"options\":{\"manufacturerCode\":5264}}"' ``` These are idempotent — re-sending a request that matches the current