Completes the Home Assistant rotation work. Rotates all three HA long-lived access tokens via the secret-rotator binary's ha-token mode. All three tested end-to-end against live HA before shipping; two were rotated for real.
The three tokens
Token
Consumer
Reload on rotation
prometheus-token ("ServiceMonitor")
Prometheus ServiceMonitor
config-reloader, no restart
shelly-manager-llat ("Shelly Manager")
shelly-manager deploy (startup env)
added to its reload annotation
config-sync-token ("Config Sync")
config-sync sidecar in the HA pod (startup env)
HA Deployment reload annotation
The binary identifies the outgoing token by its JWT iss, not client_name — the stored names are misleading (prometheus-token is "ServiceMonitor"; config-sync-token was "laptop local" until fixed earlier) — mints under a unique name (HA rejects a duplicate), and revokes the old token over a fresh new-token session.
Network — the first in-cluster rotation target
HA needs both netpol halves. The openbao default-deny only permits TCP egress to openbao/kube-dns/apiserver/world; the external-API rotators (Cloudflare, NetBird, Proxmox) ride world, but HA is in-cluster:
home-assistant-rotator-ingress (home-assistant ns): rotator → HA :8123
rotator-home-assistant-egress (openbao ns): the egress complement
Reaches HA on :8123 (HA itself, not the :8124 XFF-anchor proxy — granting :8124 would let it spoof X-Forwarded-For) via the home-assistant-bgp ClusterIP; the public name hairpins off the LB. Both are dedicated policies scoped by the rotator label, not rules on the namespace-wide default-deny (which would leak the grant to every pod on that port — the trap that once let shelly-manager ride the code-server allow).
config-sync restarts HA quarterly — deliberate
config-sync reads its token at startup and lives in the HA pod, so picking up a rotation means restarting the HA pod (~1-2 min, 04:05 off-hours, quarterly). Chosen over leaving a full-admin token un-rotated. prometheus and shelly-manager reload without touching HA.
Test evidence (live)
prometheus-token rotated end-to-end: OpenBao 6af3e8e6→991bd421, old token gone, new authenticates → HA /api/prometheus200, 22170 metric lines. Job log: minted 'ServiceMonitor [77adb8]', revoked old 'ServiceMonitor' (592eeaad...) — clean, no false warning.
Both were rotated as real tests; the committed CronJobs reconcile to the same state. Image pinned by digest sha256:159e5888… (the binary with all three correctness fixes from #318/#320/#321). OpenBao policy/role bootstrapped out-of-band, committed for provenance.
Completes the Home Assistant rotation work. Rotates all three HA long-lived access tokens via the `secret-rotator` binary's `ha-token` mode. **All three tested end-to-end against live HA** before shipping; two were rotated for real.
## The three tokens
| Token | Consumer | Reload on rotation |
|---|---|---|
| `prometheus-token` ("ServiceMonitor") | Prometheus ServiceMonitor | config-reloader, no restart |
| `shelly-manager-llat` ("Shelly Manager") | shelly-manager deploy (startup env) | **added** to its reload annotation |
| `config-sync-token` ("Config Sync") | config-sync sidecar in the HA pod (startup env) | **HA Deployment** reload annotation |
The binary identifies the outgoing token by its JWT `iss`, not `client_name` — the stored names are misleading (`prometheus-token` is "ServiceMonitor"; `config-sync-token` was "laptop local" until fixed earlier) — mints under a unique name (HA rejects a duplicate), and revokes the old token over a fresh new-token session.
## Network — the first in-cluster rotation target
HA needs **both** netpol halves. The openbao default-deny only permits TCP egress to openbao/kube-dns/apiserver/`world`; the external-API rotators (Cloudflare, NetBird, Proxmox) ride `world`, but HA is in-cluster:
- `home-assistant-rotator-ingress` (home-assistant ns): rotator → HA `:8123`
- `rotator-home-assistant-egress` (openbao ns): the egress complement
Reaches HA on **`:8123`** (HA itself, not the `:8124` XFF-anchor proxy — granting `:8124` would let it spoof X-Forwarded-For) via the `home-assistant-bgp` ClusterIP; the public name hairpins off the LB. Both are dedicated policies scoped by the rotator label, not rules on the namespace-wide default-deny (which would leak the grant to every pod on that port — the trap that once let shelly-manager ride the code-server allow).
## config-sync restarts HA quarterly — deliberate
config-sync reads its token at startup and lives in the HA pod, so picking up a rotation means restarting the HA pod (~1-2 min, 04:05 off-hours, quarterly). Chosen over leaving a full-admin token un-rotated. prometheus and shelly-manager reload without touching HA.
## Test evidence (live)
- **prometheus-token** rotated end-to-end: OpenBao `6af3e8e6`→`991bd421`, old token gone, new authenticates → HA `/api/prometheus` **200, 22170 metric lines**. Job log: `minted 'ServiceMonitor [77adb8]', revoked old 'ServiceMonitor' (592eeaad...)` — clean, no false warning.
- **shelly-manager-llat** rotated end-to-end: OpenBao updated, shelly-manager restarted onto it, **0** HA-auth errors, `state: RUNNING`.
Both were rotated as real tests; the committed CronJobs reconcile to the same state. Image pinned by digest `sha256:159e5888…` (the binary with all three correctness fixes from #318/#320/#321). OpenBao policy/role bootstrapped out-of-band, committed for provenance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Rotates prometheus-token, config-sync-token and shelly-manager-llat via the
secret-rotator binary's ha-token mode (HA mints/revokes LLATs only over its
WebSocket API). All three tested end-to-end against live HA before shipping.
Network: HA is the first IN-CLUSTER rotation target, so it needs BOTH netpol
halves — the openbao default-deny only permits TCP egress to
openbao/kube-dns/apiserver/world (the external-API rotators ride `world`):
- home-assistant-rotator-ingress (home-assistant ns): rotator -> HA :8123
- rotator-home-assistant-egress (openbao ns): the egress complement
Reaches HA on :8123 (HA itself, not the :8124 XFF-anchor proxy) via the
home-assistant-bgp Service ClusterIP; the public name hairpins off the LB.
Both dedicated policies, scoped by the rotator label, not rules on the
namespace-wide default-deny (which would leak to every pod on that port).
Reloader wiring — each token's consumer picks up the new value differently:
- prometheus-token: Prometheus config-reloader auto-reloads; no restart.
- shelly-manager-llat: added to shelly-manager's reload annotation (it reads
the token as a startup env var).
- config-sync-token: the config-sync sidecar reads it at startup and lives in
the HA pod, so the HA Deployment gets a reload annotation. This restarts HA
itself quarterly (04:05, off-hours) — accepted so the full-admin token is
lifetime-bounded like the others.
The binary identifies the outgoing token by its JWT `iss`, not client_name
(the stored names are misleading — prometheus-token is "ServiceMonitor",
config-sync-token was "laptop local"), mints under a unique name (HA rejects a
duplicate), and revokes the old token over a fresh new-token session.
OpenBao policy/role bootstrapped out-of-band (rotator-home-assistant.hcl,
committed for provenance; not Flux-managed).
Tested: prometheus-token and shelly-manager-llat rotated live end-to-end —
OpenBao updated, ESO synced, consumer restarted/reloaded, old token revoked and
gone, new token authenticates (Prometheus scrape 200 with 22k metrics;
shelly-manager 0 HA-auth errors).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Completes the Home Assistant rotation work. Rotates all three HA long-lived access tokens via the
secret-rotatorbinary'sha-tokenmode. All three tested end-to-end against live HA before shipping; two were rotated for real.The three tokens
prometheus-token("ServiceMonitor")shelly-manager-llat("Shelly Manager")config-sync-token("Config Sync")The binary identifies the outgoing token by its JWT
iss, notclient_name— the stored names are misleading (prometheus-tokenis "ServiceMonitor";config-sync-tokenwas "laptop local" until fixed earlier) — mints under a unique name (HA rejects a duplicate), and revokes the old token over a fresh new-token session.Network — the first in-cluster rotation target
HA needs both netpol halves. The openbao default-deny only permits TCP egress to openbao/kube-dns/apiserver/
world; the external-API rotators (Cloudflare, NetBird, Proxmox) rideworld, but HA is in-cluster:home-assistant-rotator-ingress(home-assistant ns): rotator → HA:8123rotator-home-assistant-egress(openbao ns): the egress complementReaches HA on
:8123(HA itself, not the:8124XFF-anchor proxy — granting:8124would let it spoof X-Forwarded-For) via thehome-assistant-bgpClusterIP; the public name hairpins off the LB. Both are dedicated policies scoped by the rotator label, not rules on the namespace-wide default-deny (which would leak the grant to every pod on that port — the trap that once let shelly-manager ride the code-server allow).config-sync restarts HA quarterly — deliberate
config-sync reads its token at startup and lives in the HA pod, so picking up a rotation means restarting the HA pod (~1-2 min, 04:05 off-hours, quarterly). Chosen over leaving a full-admin token un-rotated. prometheus and shelly-manager reload without touching HA.
Test evidence (live)
6af3e8e6→991bd421, old token gone, new authenticates → HA/api/prometheus200, 22170 metric lines. Job log:minted 'ServiceMonitor [77adb8]', revoked old 'ServiceMonitor' (592eeaad...)— clean, no false warning.state: RUNNING.Both were rotated as real tests; the committed CronJobs reconcile to the same state. Image pinned by digest
sha256:159e5888…(the binary with all three correctness fixes from #318/#320/#321). OpenBao policy/role bootstrapped out-of-band, committed for provenance.🤖 Generated with Claude Code
Rotates prometheus-token, config-sync-token and shelly-manager-llat via the secret-rotator binary's ha-token mode (HA mints/revokes LLATs only over its WebSocket API). All three tested end-to-end against live HA before shipping. Network: HA is the first IN-CLUSTER rotation target, so it needs BOTH netpol halves — the openbao default-deny only permits TCP egress to openbao/kube-dns/apiserver/world (the external-API rotators ride `world`): - home-assistant-rotator-ingress (home-assistant ns): rotator -> HA :8123 - rotator-home-assistant-egress (openbao ns): the egress complement Reaches HA on :8123 (HA itself, not the :8124 XFF-anchor proxy) via the home-assistant-bgp Service ClusterIP; the public name hairpins off the LB. Both dedicated policies, scoped by the rotator label, not rules on the namespace-wide default-deny (which would leak to every pod on that port). Reloader wiring — each token's consumer picks up the new value differently: - prometheus-token: Prometheus config-reloader auto-reloads; no restart. - shelly-manager-llat: added to shelly-manager's reload annotation (it reads the token as a startup env var). - config-sync-token: the config-sync sidecar reads it at startup and lives in the HA pod, so the HA Deployment gets a reload annotation. This restarts HA itself quarterly (04:05, off-hours) — accepted so the full-admin token is lifetime-bounded like the others. The binary identifies the outgoing token by its JWT `iss`, not client_name (the stored names are misleading — prometheus-token is "ServiceMonitor", config-sync-token was "laptop local"), mints under a unique name (HA rejects a duplicate), and revokes the old token over a fresh new-token session. OpenBao policy/role bootstrapped out-of-band (rotator-home-assistant.hcl, committed for provenance; not Flux-managed). Tested: prometheus-token and shelly-manager-llat rotated live end-to-end — OpenBao updated, ESO synced, consumer restarted/reloaded, old token revoked and gone, new token authenticates (Prometheus scrape 200 with 22k metrics; shelly-manager 0 HA-auth errors). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>