From 55f5aefdbeb841bb1bb3efb235a599aa39d69cde Mon Sep 17 00:00:00 2001 From: jklein Date: Sun, 23 Aug 2026 19:07:57 +0200 Subject: [PATCH] homeassistant: cover the secret reference, document the content gap Home Assistant reads configuration.yaml, http-config.yaml and recorder-config.yaml from a 1Password-backed Secret, mounted with subPath. Two different gaps hide behind that, and only one of them can be closed. Repointing secrets.configSecretRef at a different vault item used to leave the running pod on the old configuration. That is now covered: the pod template carries checksum/secret-ref over the rendered OnePasswordItem. The annotation is named apart from checksum/config on purpose - it only promises that the config source changed, not the config itself - matching the pattern minecraft-server introduced. Changing the CONTENT of the item in the vault still does not restart anything, and cannot: Helm never sees the content, so there is nothing to hash. The operator's own auto-restart annotation is no way out either - onepassword- operator 1.12.0 enumerates exactly one workload type in its secret-update handler (appsv1.DeploymentList) and this workload is a StatefulSet, so the annotation would be a silent no-op advertising a guarantee it cannot keep. It is deliberately not set. values.yaml states the limitation and the manual rollout restart it requires; the README guideline records the operator finding, the annotation precedence trap, and the checksum/secret-ref pattern so neither is rediscovered the hard way. Major bump: runtime behavior changes - changing the secret reference now costs a restart. For this release the effect is preventive, since it currently runs at zero replicas via scaleDown. --- README.md | 9 +++++++ homeassistant/Chart.yaml | 2 +- .../templates/homeassistant.sfs.yaml | 12 +++++++++ homeassistant/values.yaml | 27 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f883ae9..284e169 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,15 @@ These guidelines are **binding for every current and future chart** in this repo - **The annotation is content-based, not time-based**: the hash is taken over the rendered ConfigMap, so an unchanged config renders an identical hash and `helm upgrade` is a no-op. It never degenerates into a restart-on-every-upgrade. - Adding the annotation to an existing chart is a **major** bump (§2): runtime behavior changes — a config change now costs a restart. Name that cost in the PR (some workloads take minutes to come back). - **Secrets from `OnePasswordItem` cannot use this mechanism.** Helm never sees their content — the 1Password operator fills the Secret in-cluster after rendering, so there is nothing to hash. A chart that mounts such a Secret as a file documents the limitation in its `values.yaml` where it cannot close the gap. +- **What *can* be covered is the secret's `op://` path**, via a **separately named** `checksum/secret-ref` annotation over the rendered `OnePasswordItem`: + + ```yaml + checksum/secret-ref: {{ include (print $.Template.BasePath "/.secret.yaml") . | sha256sum }} + ``` + + Repointing `secrets.SecretRef` at a different vault item then takes effect instead of silently leaving the old credentials in the running pod. The separate name is deliberate: `checksum/config` promises "the config changed", `checksum/secret-ref` only "the config *source* changed" — merging them into one annotation would overstate the weaker guarantee. Reference implementations: `minecraft-server`, `homeassistant`. +- **`operator.1password.io/auto-restart` only works for Deployments — do not set it on a StatefulSet.** Verified against onepassword-operator **1.12.0** (`pkg/onepassword/secret_update_handler.go`): `restartWorkloadsWithUpdatedSecrets` enumerates exactly one workload type, `appsv1.DeploymentList`, and `getPodTemplate` accepts only `*appsv1.Deployment`. The RBAC role grants `statefulsets`, and v1.11.0 generalized the restart code (`restartWorkload`, pod-template stamping via `operator.1password.io/last-restarted`), but the PR that would add StatefulSets is still open — so the annotation on a StatefulSet is a silent no-op. Setting it there is **worse than not setting it**: it advertises a guarantee that does not exist. Affected charts (`matrix-synapse`, `samba`, `homeassistant`) instead document the manual `kubectl rollout restart statefulset --sfs`. Re-check this when the operator version is bumped; a Deployment-based chart may set the annotation. +- If a Deployment-based chart ever does set it, mind the precedence: the annotation on the **`OnePasswordItem`** is copied onto the Kubernetes Secret and read first, so a `false` there **overrides** a `true` on the workload — the opposite of what the operator's usage guide suggests. Lookup order is Secret (from the `OnePasswordItem`) → workload → namespace → the operator-global `AUTO_RESTART` env var. The trigger is the 1Password item's **version counter**, not its content, so a re-save with identical values still restarts. ### 10. Environment variables diff --git a/homeassistant/Chart.yaml b/homeassistant/Chart.yaml index fcc96cb..b124af7 100644 --- a/homeassistant/Chart.yaml +++ b/homeassistant/Chart.yaml @@ -4,7 +4,7 @@ description: A Helm chart to deploy Home Assistant on Kubernetes icon: https://www.home-assistant.io/images/home-assistant-logo.svg type: application -version: 4.2.0+up2026.8.3 +version: 5.0.0+up2026.8.3 appVersion: "2026.8.3" diff --git a/homeassistant/templates/homeassistant.sfs.yaml b/homeassistant/templates/homeassistant.sfs.yaml index 42aa274..2c9350b 100644 --- a/homeassistant/templates/homeassistant.sfs.yaml +++ b/homeassistant/templates/homeassistant.sfs.yaml @@ -15,6 +15,18 @@ spec: labels: app: "{{ .Release.Name }}-{{ .Chart.Name }}" name: "{{ .Release.Name }}-{{ .Chart.Name }}-sfs" + annotations: + # Deliberately NOT a hash of the secret's content: the Kubernetes Secret + # is materialized by the 1Password operator, so Helm never sees what is + # in it. This hash covers the rendered OnePasswordItem, i.e. the op:// + # item path - repointing secrets.configSecretRef at a different item now + # takes effect instead of silently leaving the old configuration in the + # running pod. A change of the item's CONTENT inside the vault is not + # covered; see the comment on secrets in values.yaml for why that gap + # cannot be closed here and what it means operationally. + # The name differs from checksum/config on purpose - that one promises + # "the config changed", this one only "the config source changed". + checksum/secret-ref: {{ include (print $.Template.BasePath "/homeassistant-core-config.secret.yaml") . | sha256sum }} spec: automountServiceAccountToken: false securityContext: diff --git a/homeassistant/values.yaml b/homeassistant/values.yaml index c252504..b1abb9d 100644 --- a/homeassistant/values.yaml +++ b/homeassistant/values.yaml @@ -122,5 +122,32 @@ core: # - config # - http-config # - recorder-config +# +# Changing the op:// path below IS covered: the StatefulSet carries a +# checksum/secret-ref annotation over the rendered OnePasswordItem, so pointing +# this value at a different vault item restarts the pod. +# +# Known limitation (#82): changing the CONTENT of that item in the vault does +# NOT restart Home Assistant. The operator updates the Kubernetes Secret, but +# the running pod keeps the old files - they are mounted with subPath, which the +# kubelet never refreshes, and Home Assistant reads its configuration only at +# start. The change then takes effect at some unrelated later restart, possibly +# months afterwards, and a broken value surfaces long after the edit that caused +# it. That is especially likely for this release, which is currently kept at +# zero replicas via scaleDown: whoever brings it back up gets whatever the vault +# holds at that moment. +# +# The checksum/config mechanism the other charts use cannot close this gap: +# Helm never sees the content (the operator fills the Secret in-cluster after +# rendering), so there is nothing to hash. The operator's own +# "operator.1password.io/auto-restart" annotation does not close it either - it +# only ever restarts Deployments, never StatefulSets. Verified against +# onepassword-operator 1.12.0: pkg/onepassword/secret_update_handler.go lists +# exactly one workload type (appsv1.DeploymentList), and the PR adding +# StatefulSets is still open. Setting the annotation here would look like a fix +# and do nothing, so this chart deliberately does not set it. +# +# After changing the item, restart Home Assistant by hand: +# kubectl rollout restart statefulset -homeassistant-sfs secrets: configSecretRef: null