Compare commits

...
Author SHA1 Message Date
atlas-botandClaude Opus 5.5 7b6cff3210 feat(mqtt): drop password_file and acl_file, last legacy users gone (REN-1307)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
The edge-1 bridges moved from the legacy users sml2mqtt and open3e to the
register account edge-1 (homelab#1201, #1202; broker log 10:38:53Z
u'edge-1'). Nothing else logged in as either user in 10 days. With them
gone, the broker has one account source, Dynamic Security, and this is
the last broker restart for an account change.

Tested on eclipse-mosquitto 2.0.22 with the ConfigMap's global options:
starts without the files; dynsec password client publishes within its
ACL; subscribe outside it is denied; wrong password, anonymous and the
old user open3e are refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 10:44:05 +00:00
atlas-botandClaude Opus 5.5 664e73faf4 feat(mqtt): also remove the unused legacy user admin (REN-802)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
No login in the 10 days before 2026-10-08 (Loki), and no human uses the
broker. Folded into this PR so the broker restarts once for both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 09:53:30 +00:00
atlas-botandClaude Opus 5.5 3841a6cbd5 feat(mqtt): remove legacy broker users homeassistant, zigbee2mqtt, evcc (REN-953)
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
All three clients log in by certificate now (home-assistant via the
ghostunnel sidecar, zigbee2mqtt-bridge, evcc-client). Drops their passwd
lines, ACL blocks and password env from the broker. Their Secrets,
ExternalSecret and bootstrap Jobs stay for the REN-802 cleanup step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 04:21:52 +00:00
2 changed files with 17 additions and 123 deletions
@@ -77,26 +77,13 @@ data:
allow_anonymous false
# Two account sources side by side (REN-802 step 2, tested on 2.0.22 in
# REN-900, document `coexistence`):
# Dynamic Security: accounts from the register mqtt-accounts.yaml,
# managed live by mqtt-account-controller over $CONTROL.
# password_file/acl_file: the legacy users, unchanged until each client
# moves to the register (step 3).
# Dynamic Security is asked first. For a username it does not know, and
# for a request none of its roles match, it defers to the files. A
# register entry with the same name as a legacy user therefore replaces
# that user's password, while its acl_file block still applies to
# whatever the entry's roles do not cover. Delete the legacy user when
# its client moves.
#
# The acl_file check of 2.0 allows every SUBSCRIBE and checks only on
# delivery, so the Dynamic Security default must deny publishClientReceive.
# Otherwise every Dynamic Security client could read every topic.
# One account source (REN-802): Dynamic Security, holding the register
# mqtt-accounts.yaml, managed live by mqtt-account-controller over
# $CONTROL. The legacy password_file/acl_file are gone (REN-953,
# REN-1307). Every allowed SUBSCRIBE needs a subscribePattern ACL; the
# controller writes one for each `read` entry.
plugin /usr/lib/mosquitto_dynamic_security.so
plugin_opt_config_file /mosquitto/auth/dynamic-security.json
password_file /mosquitto/auth/passwd
acl_file /mosquitto/auth/acl
# Dynamic Security start file, used until mqtt-account-controller has
# written the Secret mqtt-dynsec-seed. No secrets: only the controller's
# certificate client and a role that reaches the Dynamic Security API and
@@ -54,78 +54,19 @@ spec:
# follow-up as any other broker restart (see mosquitto-config.yaml).
image: eclipse-mosquitto:2.0.22 # {"$imagepolicy": "flux-system:mosquitto"}
# The shim runs as root (image sets no USER) and prepares the
# auth files for the broker, which drops to user mosquitto
# (UID 1883) before reading them — so files must be owned by
# mosquitto, not root. Across container restarts the emptyDir
# persists, so the passwd file already exists; rm -f keeps
# mosquitto_passwd -c (which uses O_EXCL) idempotent.
# Dynamic Security file for the broker, which drops to user
# mosquitto (UID 1883) before reading it, so the file must be
# owned by mosquitto, not root.
command:
- sh
- -c
- |
set -eu
rm -f /mosquitto/auth/passwd
mosquitto_passwd -c -b /mosquitto/auth/passwd homeassistant "$HA_PASSWORD"
mosquitto_passwd -b /mosquitto/auth/passwd admin "$ADMIN_PASSWORD"
mosquitto_passwd -b /mosquitto/auth/passwd sml2mqtt "$SML2MQTT_PASSWORD"
mosquitto_passwd -b /mosquitto/auth/passwd open3e "$OPEN3E_PASSWORD"
mosquitto_passwd -b /mosquitto/auth/passwd evcc "$EVCC_PASSWORD"
mosquitto_passwd -b /mosquitto/auth/passwd zigbee2mqtt "$ZIGBEE2MQTT_PASSWORD"
# ACL: HA keeps full access (subscribes to everything). Shellies
# are not legacy users any more: each has its own Dynamic Security
# account from mqtt-accounts.yaml (REN-802, REN-1017).
# `admin` is a workstation/debugging credential — full
# readwrite, kept on its own credential so its password can
# be rotated independently of the HA session.
# `sml2mqtt` is the power-meter bridge — WRITE-only under its own
# `sml2mqtt/#` subtree (it only publishes; HA reads it via the
# full-access homeassistant user).
# `open3e` is the Viessmann E3 CAN bridge (Vitocal/VX3) —
# scoped to its own `open3e/#` subtree: publishes datapoints
# and later subscribes to `open3e/cmnd` in listener mode (HA
# reads via the full-access homeassistant user).
# `evcc` is the (cross-namespace) EV charge controller — reads
# `open3e/#` (VX3 grid/PV/battery/SoC datapoints), publishes its
# own state under `evcc/#`, and reads the grid price from
# `energy/#` (HA republishes input_number.strompreis to
# energy/tariff/grid; kept OUTSIDE evcc/# so evcc's own retained-
# topic management can't wipe it).
cat > /mosquitto/auth/acl <<'EOF'
user homeassistant
topic readwrite #
user admin
topic readwrite #
user sml2mqtt
topic write sml2mqtt/#
user open3e
topic readwrite open3e/#
user evcc
topic read open3e/#
topic readwrite evcc/#
topic read energy/#
# `zigbee2mqtt` is the Zigbee2MQTT bridge (the ZHA replacement).
# Owns its own `zigbee2mqtt/#` subtree; may WRITE `homeassistant/#`
# to publish HA MQTT-discovery configs (write-only there, to deny
# discovery-tree recon from a compromised bridge); and READs `homeassistant/status`, HA's birth/will topic.
#
# That read grant does NOT buy a discovery re-publish: on
# `online`, Z2M 2.12.1 republishes bridge/state and cached device
# states only (lib/extension/homeassistant.ts onMQTTMessage) —
# discover() runs solely from start(). The grant is still correct,
# because the cached-state republish is what makes HA show current
# values right after an HA restart. Restoring discovery after a
# BROKER restart is handled by the discovery-guard sidecar in
# zigbee2mqtt-deployment.yaml.
user zigbee2mqtt
topic readwrite zigbee2mqtt/#
topic write homeassistant/#
topic read homeassistant/status
EOF
# No password_file/acl_file any more (REN-802, REN-1307): every
# client has its own account in mqtt-accounts.yaml, managed by
# mqtt-account-controller through Dynamic Security. The last
# legacy users, the edge-1 bridges sml2mqtt and open3e, moved
# to the register account edge-1.
# Dynamic Security start file (REN-802): the controller's seed
# if it exists, else the static base file. The seed carries
# every register account and its hash, so the broker is in the
@@ -139,43 +80,9 @@ spec:
cp /mosquitto/config/dynamic-security.json /mosquitto/auth/dynamic-security.json
echo "dynsec: no seed yet, loaded base file"
fi
chown 1883:1883 /mosquitto/auth /mosquitto/auth/passwd /mosquitto/auth/acl /mosquitto/auth/dynamic-security.json
chmod 0600 /mosquitto/auth/passwd /mosquitto/auth/acl /mosquitto/auth/dynamic-security.json
chown 1883:1883 /mosquitto/auth /mosquitto/auth/dynamic-security.json
chmod 0600 /mosquitto/auth/dynamic-security.json
exec mosquitto -c /mosquitto/config/mosquitto.conf
env:
- name: HA_PASSWORD
valueFrom:
secretKeyRef:
name: mosquitto-credentials
key: homeassistant-password
- name: ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: mosquitto-admin-credentials
key: password
- name: SML2MQTT_PASSWORD
valueFrom:
secretKeyRef:
name: mosquitto-sml2mqtt-credentials
key: password
- name: OPEN3E_PASSWORD
valueFrom:
secretKeyRef:
name: mosquitto-open3e-credentials
key: password
- name: EVCC_PASSWORD
valueFrom:
secretKeyRef:
name: mosquitto-evcc-credentials
key: password
# Zigbee2MQTT bridge credential (mosquitto-zigbee2mqtt-credentials-
# bootstrap). Hashed into the passwd file here; Z2M reads the same
# Secret via its init container to authenticate to the broker.
- name: ZIGBEE2MQTT_PASSWORD
valueFrom:
secretKeyRef:
name: mosquitto-zigbee2mqtt-credentials
key: password
ports:
- name: mqtt
containerPort: 1883
@@ -231,12 +138,12 @@ spec:
# Service and the probe dials 127.0.0.1; the chain is still checked
# against mqtt-ca.
#
# mosquitto is a single-replica SPOF (HA, Shelly, sml2mqtt, open3e,
# mosquitto is a single-replica SPOF (HA, Shelly, edge-1 bridge,
# evcc). Liveness is deliberately slow to fire — failureThreshold 6
# x 30s = 3 min of sustained failure before a restart — so a
# transient hiccup never bounces the broker every client depends
# on. The startupProbe (tcpSocket) suspends both while the
# container builds the passwd/acl files and mosquitto binds :1883.
# container copies the Dynamic Security file and mosquitto binds :1883.
startupProbe:
tcpSocket:
port: mqtt