Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d905416bc | ||
|
|
b24442ab71 |
@@ -12,7 +12,8 @@ resources:
|
|||||||
- vscode-ingress.yaml
|
- vscode-ingress.yaml
|
||||||
- prometheus-token-externalsecret.yaml
|
- prometheus-token-externalsecret.yaml
|
||||||
- config-sync-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-config.yaml
|
||||||
- mosquitto-certificate.yaml
|
- mosquitto-certificate.yaml
|
||||||
- mosquitto-service.yaml
|
- mosquitto-service.yaml
|
||||||
@@ -36,8 +37,9 @@ resources:
|
|||||||
- esphome-ingress.yaml
|
- esphome-ingress.yaml
|
||||||
# Zigbee2MQTT — ZHA replacement bridge. Ships scaled to 0; the cutover
|
# Zigbee2MQTT — ZHA replacement bridge. Ships scaled to 0; the cutover
|
||||||
# runbook (zigbee2mqtt-migration-runbook.md) scales it up. The MQTT
|
# runbook (zigbee2mqtt-migration-runbook.md) scales it up. The MQTT
|
||||||
# `zigbee2mqtt` broker user is provisioned by mosquitto-bootstrap.yaml
|
# `zigbee2mqtt` broker user comes from OpenBao via
|
||||||
# (above) and wired into mosquitto-deployment.yaml (passwd + ACL).
|
# mosquitto-credentials-externalsecrets.yaml (above) and wired
|
||||||
|
# into mosquitto-deployment.yaml (passwd + ACL).
|
||||||
- zigbee2mqtt-coordinator-backup-externalsecret.yaml
|
- zigbee2mqtt-coordinator-backup-externalsecret.yaml
|
||||||
- zigbee2mqtt-config.yaml
|
- zigbee2mqtt-config.yaml
|
||||||
- zigbee2mqtt-pvc.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
|
||||||
Reference in New Issue
Block a user