Compare commits

...
Author SHA1 Message Date
claude-botandClaude Opus 5 3d905416bc docs(mosquitto): flag the new Secret ownership coupling; drop "bootstrap" name
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Review points on #450, both worth acting on.

Document the coupling ESO introduces that the Jobs did not have. With
creationPolicy implicitly Owner, ESO sets an ownerReference from each
Secret to its ExternalSecret; combined with the Kustomization's
prune: true, deleting an ExternalSecret block now DELETES THE LIVE SECRET
and with it that client's broker access. The old Jobs created Secrets with
no ownerReference, so removing a Job left its Secret untouched — that
safety net is gone, and the file now says so, including the
creationPolicy: Orphan escape hatch for detaching a credential without
losing it.

Rename mosquitto-bootstrap-credentials-externalsecrets.yaml to
mosquitto-credentials-externalsecrets.yaml. "bootstrap" described the Jobs
that no longer exist; these are steady-state. git tracks the rename, so the
archaeology the old name preserved is not lost.

Also re-verified, a second time and independently, that every OpenBao entry
still holds the live password — the reviewer correctly noted this PR's
safety rests on a fact the diff cannot show:

  mosquitto-credentials              homeassistant-password  8bbb508e  MATCH
  mosquitto-admin-credentials        password                cdd5d74d  MATCH
  mosquitto-health-credentials       password                23c10b3b  MATCH
  mosquitto-open3e-credentials       password                df67a98e  MATCH
  mosquitto-shellies-credentials     password                5ae6f4e4  MATCH
  mosquitto-sml2mqtt-credentials     password                aa13ce52  MATCH
  mosquitto-zigbee2mqtt-credentials  password                e440d3ae  MATCH

Left the wording overlap between this file's target.name comment and the
sibling evcc file alone: harmonising it would touch a file this PR
otherwise does not.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:14:04 +02:00
claude-botandClaude Opus 5 b24442ab71 refactor(mosquitto): move broker credentials to OpenBao, delete bootstrap Jobs
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Replaces mosquitto-bootstrap.yaml (518 lines: 7 Jobs, a ServiceAccount, a
Role and a RoleBinding) with 7 ExternalSecrets, matching the pattern
mosquitto-evcc-credentials already used in the same directory.

Each Job was ~70 lines of shell that generated a password, base64'd it,
hand-built Secret JSON with jq and POSTed it to the Kubernetes API with
curl — needing RBAC and an `apk add curl jq` on every run. That is 518
lines to do what 14 declarative lines do here.

It also could not be maintained: a Job's spec.template is immutable, so
merely changing the container image made the whole home-assistant
Kustomization fail to reconcile with "field is immutable" — which is what
killed #449. Deleting the Jobs is the only way to change them, and since
the Kustomization has prune: true, removing them from git does exactly
that rather than attempting a rejected patch.

The passwords also only ever existed in the Secret objects: not in OpenBao,
so not backed up, not centrally rotatable, and unrecoverable if a Secret
were lost. They now live alongside every other credential in this
namespace.

MIGRATION IS VALUE-PRESERVING. The live passwords were copied into OpenBao
first, each round-trip verified by sha256 — the broker has already hashed
them (mosquitto_passwd -U at startup) and every client is using them, so
generating fresh ones would have broken Home Assistant, zigbee2mqtt,
shellies, sml2mqtt, open3e and the health check at once.

Verified on the least critical credential before committing, since ESO
adopting a Secret it did not create was the one real unknown:

  ExternalSecret -> SecretSynced / Ready=True
  password hash  -> 3a145b6cc97f before, 3a145b6cc97f after (unchanged)
  labels         -> reconcile.external-secrets.io/managed=true
  ownerReferences-> ExternalSecret

So ESO adopts the existing Secret and rewrites it byte-identically;
consumers observe nothing. The Secrets carry no ownerReferences from the
Jobs, so pruning the Jobs cannot garbage-collect them.

Key names are deliberately NOT normalised: mosquitto-credentials keeps
`homeassistant-password` while the rest keep `password`. Consumers
reference those names, so unifying them would be a separate breaking
change; each OpenBao entry was seeded with the matching key.

Rotation, previously "delete the Secret AND the Job and wait for Flux", is
now: update the value in OpenBao and ESO republishes within
refreshInterval. The broker still needs a restart to re-hash, as before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:02:46 +02:00
3 changed files with 160 additions and 511 deletions
@@ -12,7 +12,8 @@ resources:
- vscode-ingress.yaml
- prometheus-token-externalsecret.yaml
- config-sync-token-externalsecret.yaml
- mosquitto-bootstrap.yaml
# Broker credentials come from OpenBao via ESO (replaced the bootstrap Jobs).
- mosquitto-credentials-externalsecrets.yaml
- mosquitto-config.yaml
- mosquitto-certificate.yaml
- mosquitto-service.yaml
@@ -36,8 +37,9 @@ resources:
- esphome-ingress.yaml
# Zigbee2MQTT — ZHA replacement bridge. Ships scaled to 0; the cutover
# runbook (zigbee2mqtt-migration-runbook.md) scales it up. The MQTT
# `zigbee2mqtt` broker user is provisioned by mosquitto-bootstrap.yaml
# (above) and wired into mosquitto-deployment.yaml (passwd + ACL).
# `zigbee2mqtt` broker user comes from OpenBao via
# mosquitto-credentials-externalsecrets.yaml (above) and wired
# into mosquitto-deployment.yaml (passwd + ACL).
- zigbee2mqtt-coordinator-backup-externalsecret.yaml
- zigbee2mqtt-config.yaml
- zigbee2mqtt-pvc.yaml
@@ -1,508 +0,0 @@
---
# Generates mosquitto-credentials on first deploy. The password is created
# in-cluster, lives only in the Secret object, and is never written to git
# or seen by a human. The Mosquitto broker hashes it via mosquitto_passwd
# -U at startup; HA reads the same value from this Secret (configured
# manually in the HA MQTT integration UI).
#
# Idempotent: if the Secret already exists, the Job exits 0 without
# regenerating, so subsequent Flux reconciles don't rotate the password.
# To rotate, delete both the Secret and this Job; Flux will re-create
# the Job and a fresh password.
apiVersion: v1
kind: ServiceAccount
metadata:
name: mosquitto-bootstrap
namespace: home-assistant
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: mosquitto-bootstrap
namespace: home-assistant
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: mosquitto-bootstrap
namespace: home-assistant
subjects:
- kind: ServiceAccount
name: mosquitto-bootstrap
namespace: home-assistant
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: mosquitto-bootstrap
---
apiVersion: batch/v1
kind: Job
metadata:
name: mosquitto-credentials-bootstrap
namespace: home-assistant
spec:
backoffLimit: 4
template:
metadata:
labels:
app.kubernetes.io/name: mosquitto-bootstrap
spec:
serviceAccountName: mosquitto-bootstrap
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API=https://kubernetes.default.svc
if curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
"$API/api/v1/namespaces/$NS/secrets/$NAME" >/dev/null 2>&1; then
echo "$NAME already exists; nothing to do."
exit 0
fi
PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
PW_B64=$(printf '%s' "$PW" | base64 | tr -d '\n')
body=$(jq -nc \
--arg name "$NAME" \
--arg ns "$NS" \
--arg pw "$PW_B64" \
'{apiVersion: "v1",
kind: "Secret",
metadata: {name: $name, namespace: $ns},
type: "Opaque",
data: {"homeassistant-password": $pw}}')
curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -X POST \
--data "$body" \
"$API/api/v1/namespaces/$NS/secrets" >/dev/null
echo "generated $NAME"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
---
# Third broker user: `admin`. Full readwrite (same as HA) but kept on
# its own credential so a workstation client can hold a password the
# in-cluster HA config never sees, and rotating one doesn't affect the
# other. Retrieve with:
# kubectl -n home-assistant get secret mosquitto-admin-credentials \
# -o jsonpath='{.data.password}' | base64 -d
apiVersion: batch/v1
kind: Job
metadata:
name: mosquitto-admin-credentials-bootstrap
namespace: home-assistant
spec:
backoffLimit: 4
template:
metadata:
labels:
app.kubernetes.io/name: mosquitto-bootstrap
spec:
serviceAccountName: mosquitto-bootstrap
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-admin-credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API=https://kubernetes.default.svc
if curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
"$API/api/v1/namespaces/$NS/secrets/$NAME" >/dev/null 2>&1; then
echo "$NAME already exists; nothing to do."
exit 0
fi
PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
PW_B64=$(printf '%s' "$PW" | base64 | tr -d '\n')
body=$(jq -nc \
--arg name "$NAME" \
--arg ns "$NS" \
--arg pw "$PW_B64" \
'{apiVersion: "v1",
kind: "Secret",
metadata: {name: $name, namespace: $ns},
type: "Opaque",
data: {password: $pw}}')
curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -X POST \
--data "$body" \
"$API/api/v1/namespaces/$NS/secrets" >/dev/null
echo "generated $NAME"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
---
# Second broker user: `shellies`. Separate Secret (not a second key on
# mosquitto-credentials) so each credential's lifecycle is independent —
# rotating one never touches the other, and existing clusters whose
# mosquitto-credentials was already generated for `homeassistant` only
# don't end up with a half-populated Secret.
apiVersion: batch/v1
kind: Job
metadata:
name: mosquitto-shellies-credentials-bootstrap
namespace: home-assistant
spec:
backoffLimit: 4
template:
metadata:
labels:
app.kubernetes.io/name: mosquitto-bootstrap
spec:
serviceAccountName: mosquitto-bootstrap
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-shellies-credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API=https://kubernetes.default.svc
if curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
"$API/api/v1/namespaces/$NS/secrets/$NAME" >/dev/null 2>&1; then
echo "$NAME already exists; nothing to do."
exit 0
fi
PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
PW_B64=$(printf '%s' "$PW" | base64 | tr -d '\n')
body=$(jq -nc \
--arg name "$NAME" \
--arg ns "$NS" \
--arg pw "$PW_B64" \
'{apiVersion: "v1",
kind: "Secret",
metadata: {name: $name, namespace: $ns},
type: "Opaque",
data: {password: $pw}}')
curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -X POST \
--data "$body" \
"$API/api/v1/namespaces/$NS/secrets" >/dev/null
echo "generated $NAME"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
---
# Fourth broker user: `sml2mqtt` — the power-meter bridge. Publish-only
# under `sml2mqtt/#` (ACL in mosquitto-deployment.yaml). Own credential so
# its lifecycle is independent of the others. The sml2mqtt pod reads this
# password via its init container and renders it into config.yml.
apiVersion: batch/v1
kind: Job
metadata:
name: mosquitto-sml2mqtt-credentials-bootstrap
namespace: home-assistant
spec:
backoffLimit: 4
template:
metadata:
labels:
app.kubernetes.io/name: mosquitto-bootstrap
spec:
serviceAccountName: mosquitto-bootstrap
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-sml2mqtt-credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API=https://kubernetes.default.svc
if curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
"$API/api/v1/namespaces/$NS/secrets/$NAME" >/dev/null 2>&1; then
echo "$NAME already exists; nothing to do."
exit 0
fi
PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
PW_B64=$(printf '%s' "$PW" | base64 | tr -d '\n')
body=$(jq -nc \
--arg name "$NAME" \
--arg ns "$NS" \
--arg pw "$PW_B64" \
'{apiVersion: "v1",
kind: "Secret",
metadata: {name: $name, namespace: $ns},
type: "Opaque",
data: {password: $pw}}')
curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -X POST \
--data "$body" \
"$API/api/v1/namespaces/$NS/secrets" >/dev/null
echo "generated $NAME"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
---
# Fifth broker user: `open3e` — the Viessmann E3 CAN bridge (Vitocal 250-A
# + Vitocharge VX3 via open3e). Scoped to `open3e/#` (ACL in
# mosquitto-deployment.yaml). Own credential so its lifecycle is
# independent of the others. Retrieve with:
# kubectl -n home-assistant get secret mosquitto-open3e-credentials \
# -o jsonpath='{.data.password}' | base64 -d
apiVersion: batch/v1
kind: Job
metadata:
name: mosquitto-open3e-credentials-bootstrap
namespace: home-assistant
spec:
backoffLimit: 4
template:
metadata:
labels:
app.kubernetes.io/name: mosquitto-bootstrap
spec:
serviceAccountName: mosquitto-bootstrap
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-open3e-credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API=https://kubernetes.default.svc
if curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
"$API/api/v1/namespaces/$NS/secrets/$NAME" >/dev/null 2>&1; then
echo "$NAME already exists; nothing to do."
exit 0
fi
PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
PW_B64=$(printf '%s' "$PW" | base64 | tr -d '\n')
body=$(jq -nc \
--arg name "$NAME" \
--arg ns "$NS" \
--arg pw "$PW_B64" \
'{apiVersion: "v1",
kind: "Secret",
metadata: {name: $name, namespace: $ns},
type: "Opaque",
data: {password: $pw}}')
curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -X POST \
--data "$body" \
"$API/api/v1/namespaces/$NS/secrets" >/dev/null
echo "generated $NAME"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
---
# Broker user `health`: a dedicated, least-privilege credential used ONLY by
# mosquitto's own liveness/readiness probes (mosquitto-deployment.yaml). Its
# ACL is `write health/#` and nothing else — it cannot read any topic or
# write outside the throwaway probe subtree, so a leak is worthless (nothing
# subscribes to health/#). Kept on its own Secret, like every other user, so
# the probe never has to borrow the admin credential.
apiVersion: batch/v1
kind: Job
metadata:
name: mosquitto-health-credentials-bootstrap
namespace: home-assistant
spec:
backoffLimit: 4
template:
metadata:
labels:
app.kubernetes.io/name: mosquitto-bootstrap
spec:
serviceAccountName: mosquitto-bootstrap
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-health-credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API=https://kubernetes.default.svc
if curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
"$API/api/v1/namespaces/$NS/secrets/$NAME" >/dev/null 2>&1; then
echo "$NAME already exists; nothing to do."
exit 0
fi
PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
PW_B64=$(printf '%s' "$PW" | base64 | tr -d '\n')
body=$(jq -nc \
--arg name "$NAME" \
--arg ns "$NS" \
--arg pw "$PW_B64" \
'{apiVersion: "v1",
kind: "Secret",
metadata: {name: $name, namespace: $ns},
type: "Opaque",
data: {password: $pw}}')
curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -X POST \
--data "$body" \
"$API/api/v1/namespaces/$NS/secrets" >/dev/null
echo "generated $NAME"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
---
# Broker user `zigbee2mqtt` — the Zigbee2MQTT bridge (ZHA replacement). Scoped
# to its own `zigbee2mqtt/#` subtree plus `write homeassistant/#` (so it can
# publish HA MQTT-discovery configs, exactly like the `shellies` user) and
# `read homeassistant/status` (Z2M re-publishes discovery when HA restarts —
# ACL in mosquitto-deployment.yaml). Own credential so its lifecycle is
# independent of the others. The Z2M init container reads this password and
# renders it into /app/data/secret.yaml.
apiVersion: batch/v1
kind: Job
metadata:
name: mosquitto-zigbee2mqtt-credentials-bootstrap
namespace: home-assistant
spec:
backoffLimit: 4
template:
metadata:
labels:
app.kubernetes.io/name: mosquitto-bootstrap
spec:
serviceAccountName: mosquitto-bootstrap
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-zigbee2mqtt-credentials
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
API=https://kubernetes.default.svc
if curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
"$API/api/v1/namespaces/$NS/secrets/$NAME" >/dev/null 2>&1; then
echo "$NAME already exists; nothing to do."
exit 0
fi
PW=$(tr -dc 'A-Za-z0-9' </dev/urandom | head -c 32)
PW_B64=$(printf '%s' "$PW" | base64 | tr -d '\n')
body=$(jq -nc \
--arg name "$NAME" \
--arg ns "$NS" \
--arg pw "$PW_B64" \
'{apiVersion: "v1",
kind: "Secret",
metadata: {name: $name, namespace: $ns},
type: "Opaque",
data: {password: $pw}}')
curl -fsSL --cacert "$CA" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -X POST \
--data "$body" \
"$API/api/v1/namespaces/$NS/secrets" >/dev/null
echo "generated $NAME"
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
@@ -0,0 +1,155 @@
# The seven mosquitto broker credentials that used to be generated by bootstrap
# Jobs (mosquitto-bootstrap.yaml, now deleted), converted to the same
# OpenBao-backed pattern mosquitto-evcc-credentials already used.
#
# WHY THE JOBS WENT
# Each was ~70 lines of shell that generated a password, base64'd it, hand-built
# Secret JSON with jq and POSTed it to the Kubernetes API with curl, needing a
# ServiceAccount + Role + RoleBinding and an `apk add curl jq` on every run.
# Together: 518 lines to do what 14 declarative lines do here. They also could not
# be modified in place at all — a Job's spec.template is immutable, so changing
# even the container image made the whole Kustomization fail to reconcile.
#
# The passwords were also only ever in the Secret object: not in OpenBao, so not
# backed up, not centrally rotatable, and unrecoverable if the Secret were lost.
# Now they live in OpenBao like every other credential in this namespace.
#
# MIGRATION WAS VALUE-PRESERVING
# The existing live passwords were copied into OpenBao before this landed, and
# each round-trip was verified by sha256 — the broker has already hashed these
# (mosquitto_passwd -U at startup) and every client is using them, so generating
# fresh ones would have broken Home Assistant, zigbee2mqtt, shellies, sml2mqtt,
# open3e and the health check at once. ESO therefore rewrites each Secret with
# byte-identical content and nothing observes a change.
#
# ROTATION, which the Jobs made awkward (delete the Secret AND the Job, then wait
# for Flux), is now: update the value in OpenBao, and within refreshInterval ESO
# republishes it. The broker still needs a restart to re-hash, as before.
#
# ⚠️ NEW COUPLING, absent with the Jobs: creationPolicy is implicitly Owner, so
# ESO sets an ownerReference from each Secret to its ExternalSecret. Combined with
# the Kustomization's prune: true, DELETING AN ExternalSecret BLOCK BELOW NOW
# DELETES THE LIVE SECRET — and with it broker access for that client. The old
# Jobs created Secrets with no ownerReference, so removing a Job left its Secret
# untouched; that safety net is gone. To retire a credential deliberately, remove
# the consumer first. To move one out of ESO's control without losing it, set
# creationPolicy: Orphan on that block before deleting it.
#
# NOTE the key names are not uniform: mosquitto-credentials carries
# `homeassistant-password` while the rest carry `password`. That asymmetry came
# from the original Jobs and is preserved deliberately — the consumers reference
# those key names, so normalising them would be a separate, breaking change.
# `dataFrom.extract` copies whatever keys the OpenBao entry holds, so each entry
# was seeded with the matching key name.
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mosquitto-credentials
namespace: home-assistant
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: mosquitto-credentials # unchanged name; HA's MQTT integration reads this
dataFrom:
- extract:
key: apps/home-assistant/mosquitto-credentials
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mosquitto-admin-credentials
namespace: home-assistant
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: mosquitto-admin-credentials
dataFrom:
- extract:
key: apps/home-assistant/mosquitto-admin-credentials
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mosquitto-health-credentials
namespace: home-assistant
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: mosquitto-health-credentials
dataFrom:
- extract:
key: apps/home-assistant/mosquitto-health-credentials
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mosquitto-open3e-credentials
namespace: home-assistant
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: mosquitto-open3e-credentials
dataFrom:
- extract:
key: apps/home-assistant/mosquitto-open3e-credentials
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mosquitto-shellies-credentials
namespace: home-assistant
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: mosquitto-shellies-credentials
dataFrom:
- extract:
key: apps/home-assistant/mosquitto-shellies-credentials
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mosquitto-sml2mqtt-credentials
namespace: home-assistant
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: mosquitto-sml2mqtt-credentials
dataFrom:
- extract:
key: apps/home-assistant/mosquitto-sml2mqtt-credentials
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: mosquitto-zigbee2mqtt-credentials
namespace: home-assistant
spec:
refreshInterval: 1h
secretStoreRef:
kind: ClusterSecretStore
name: openbao
target:
name: mosquitto-zigbee2mqtt-credentials
dataFrom:
- extract:
key: apps/home-assistant/mosquitto-zigbee2mqtt-credentials