Phase 1 of #453. One of eight broker users; the other seven are untouched and keep their bootstrap Jobs.
Why health first
It's the broker's own liveness/readiness probe credential, consumed by exec probes inside the mosquitto container via the same Secret that seeds the passwd file. Nothing outside the pod holds it — so rotating it needs no coordination with any client. Smallest possible blast radius for proving rotator → OpenBao → ESO → Reloader end to end, before touching credentials held by Shelly firmware, the edge host, or the HA config entry.
What it adds
Following rotation/paperless.yaml exactly:
rotator-mosquitto-health.hcl — per-secret policy, read+patch on that one path. Deliberately not a wildcard over mosquitto-*: users are being enrolled one at a time, and a wildcard would silently grant this rotator write access to the other seven before they're ready.
rotation/mosquitto-health.yaml — SA + quarterly CronJob, secret-rotator random mode. Scheduled a month off paperless so unrelated rotations don't share a window.
mosquitto-health-credentials-externalsecret.yaml — projects back to the same Secret name and key, so mosquitto-deployment.yaml's secretKeyRef and the probes reading $HEALTH_PASSWORD are unchanged. refreshInterval: 10m rather than evcc's 1h, so a rotation lands promptly instead of leaving the old password valid for up to an hour.
A Reloader annotation. Without it rotation is inert — the startup shim rebuilds /mosquitto/auth/passwd from the Secrets on pod start, so a rotated Secret would sit unused until some unrelated restart. Listed per-Secret rather than auto: "true", so enrolling each further user stays a visible edit.
Removes only the health bootstrap Job (508 → 440 lines). Deleting rather than editing also sidesteps the spec.template immutability that made #449 unmergeable.
Verified before committing
random mode fits unchanged. Env contract is BAO_ROLE/BAO_PATH/BAO_FIELDS/RANDOM_BYTES, and it calls assert_fields_existfirst — so it rotates an existing field and never bootstraps one. Seeding stays a documented one-time out-of-band step, as with every other rotator. That answers the first open question in #453.
Charset is safe here but not universally. Random mode emits standard base64 (+, /, =); the old Jobs emitted alphanumeric via tr -dc 'A-Za-z0-9'. The probes pass it as -P "$HEALTH_PASSWORD" inside the container, never through a URL or config file. This must be re-checked per user before enrolling sml2mqtt/open3e (edge-host templates), shellies (device firmware) or homeassistant. Noted in the rotator's header.
Merge is a no-op for clients. The OpenBao path already holds a password field and its value still matches the live Secret (both 23c10b3b), so ESO adoption changes nothing.
No ESO policy change needed — eso-read.hcl already grants homelab/data/*.
Policy + role applied out-of-band (they aren't Kubernetes resources): bound to SA mosquitto-health-secret-rotator in ns openbao, ttl 15m, verified readable.
kustomize build + server-side dry run clean on both the app and rotation kustomizations; six Jobs remain in mosquitto-bootstrap.yaml.
Deliberately not done here
The first actual rotation. It's safe (in-cluster only, self-healing) but restarts the broker, so it belongs as a post-merge acceptance test rather than bundled into a merge:
Expect the OpenBao value to change, ESO to sync within 10m, Reloader to roll the broker, and probes to pass with the new password. Note the roll uses maxUnavailable: 0/maxSurge: 1, so briefly two brokers with different passwd files sit behind the LB and a probe against the outgoing pod can fail auth — self-healing, and expected rather than alarming.
Phase 1 of #453. **One of eight** broker users; the other seven are untouched and keep their bootstrap Jobs.
## Why `health` first
It's the broker's *own* liveness/readiness probe credential, consumed by exec probes **inside the mosquitto container** via the same Secret that seeds the passwd file. Nothing outside the pod holds it — so rotating it needs no coordination with any client. Smallest possible blast radius for proving `rotator → OpenBao → ESO → Reloader` end to end, before touching credentials held by Shelly firmware, the edge host, or the HA config entry.
## What it adds
Following `rotation/paperless.yaml` exactly:
- **`rotator-mosquitto-health.hcl`** — per-secret policy, `read`+`patch` on that **one** path. Deliberately *not* a wildcard over `mosquitto-*`: users are being enrolled one at a time, and a wildcard would silently grant this rotator write access to the other seven before they're ready.
- **`rotation/mosquitto-health.yaml`** — SA + quarterly CronJob, `secret-rotator` random mode. Scheduled a month off paperless so unrelated rotations don't share a window.
- **`mosquitto-health-credentials-externalsecret.yaml`** — projects back to the **same Secret name and key**, so `mosquitto-deployment.yaml`'s `secretKeyRef` and the probes reading `$HEALTH_PASSWORD` are unchanged. `refreshInterval: 10m` rather than evcc's `1h`, so a rotation lands promptly instead of leaving the old password valid for up to an hour.
- **A Reloader annotation.** Without it rotation is **inert** — the startup shim rebuilds `/mosquitto/auth/passwd` from the Secrets on pod start, so a rotated Secret would sit unused until some unrelated restart. Listed per-Secret rather than `auto: "true"`, so enrolling each further user stays a visible edit.
Removes **only** the health bootstrap Job (508 → 440 lines). Deleting rather than editing also sidesteps the `spec.template` immutability that made #449 unmergeable.
## Verified before committing
- **`random` mode fits unchanged.** Env contract is `BAO_ROLE`/`BAO_PATH`/`BAO_FIELDS`/`RANDOM_BYTES`, and it calls `assert_fields_exist` **first** — so it rotates an existing field and never bootstraps one. Seeding stays a documented one-time out-of-band step, as with every other rotator. That answers the first open question in #453.
- **Charset is safe here but not universally.** Random mode emits standard **base64** (`+`, `/`, `=`); the old Jobs emitted alphanumeric via `tr -dc 'A-Za-z0-9'`. The probes pass it as `-P "$HEALTH_PASSWORD"` inside the container, never through a URL or config file. **This must be re-checked per user** before enrolling `sml2mqtt`/`open3e` (edge-host templates), `shellies` (device firmware) or `homeassistant`. Noted in the rotator's header.
- **Merge is a no-op for clients.** The OpenBao path already holds a `password` field and its value still matches the live Secret (both `23c10b3b`), so ESO adoption changes nothing.
- **No ESO policy change needed** — `eso-read.hcl` already grants `homelab/data/*`.
- **Policy + role applied out-of-band** (they aren't Kubernetes resources): bound to SA `mosquitto-health-secret-rotator` in ns `openbao`, ttl 15m, verified readable.
- `kustomize build` + server-side dry run clean on both the app and rotation kustomizations; six Jobs remain in `mosquitto-bootstrap.yaml`.
## Deliberately not done here
**The first actual rotation.** It's safe (in-cluster only, self-healing) but restarts the broker, so it belongs as a post-merge acceptance test rather than bundled into a merge:
```
kubectl -n openbao create job --from=cronjob/mosquitto-health-secret-rotate mosquitto-health-secret-rotate-now
```
Expect the OpenBao value to change, ESO to sync within 10m, Reloader to roll the broker, and probes to pass with the new password. Note the roll uses `maxUnavailable: 0`/`maxSurge: 1`, so briefly two brokers with different passwd files sit behind the LB and a probe against the outgoing pod can fail auth — self-healing, and expected rather than alarming.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Phase 1 of PROPOSAL-mosquitto-credential-rotation.md. One of eight broker
users; the other seven are untouched and keep their bootstrap Jobs.
`health` goes first deliberately: it is the broker's OWN liveness/readiness
probe credential, consumed by exec probes inside the mosquitto container
via the same Secret that seeds the passwd file. Nothing outside the pod
holds it, so rotating it needs no coordination with any client — the
smallest blast radius for proving rotator -> OpenBao -> ESO -> Reloader end
to end before touching credentials held by Shelly firmware, the edge host
or the HA config entry.
Adds, following rotation/paperless.yaml exactly:
- rotator-mosquitto-health.hcl — per-secret policy, read+patch on that ONE
path. Deliberately not a wildcard over mosquitto-*: the users are being
enrolled one at a time, and a wildcard would silently grant this rotator
write access to the other seven before they are ready.
- rotation/mosquitto-health.yaml — SA + quarterly CronJob running
secret-rotator in random mode. Scheduled one month off paperless so
unrelated rotations do not share a window.
- mosquitto-health-credentials-externalsecret.yaml — projects the value
back to the SAME Secret name and key, so mosquitto-deployment.yaml's
secretKeyRef and the probes reading $HEALTH_PASSWORD are unchanged.
- A Reloader annotation on the mosquitto Deployment. Without it rotation is
INERT: the startup shim rebuilds /mosquitto/auth/passwd from the Secrets
on pod start, so a rotated Secret would sit unused until some unrelated
restart while the old password stayed valid. Listed per-Secret rather
than `auto: "true"` so enrolling each further user stays a visible edit.
Removes only the health bootstrap Job (508 -> 440 lines); the remaining six
Jobs are untouched. Deleting rather than editing also sidesteps Job
spec.template immutability, which is what made the earlier attempt (#449)
unmergeable.
Verified before committing:
- random mode fits unchanged: env contract is BAO_ROLE/BAO_PATH/BAO_FIELDS/
RANDOM_BYTES, and it calls assert_fields_exist FIRST — so it rotates an
existing field and never bootstraps one. Seeding stays a documented
one-time out-of-band step, exactly as every other rotator does it.
- Charset is safe HERE but not universally: random mode emits standard
base64 (+, /, =) whereas the old Jobs emitted alphanumeric via
`tr -dc 'A-Za-z0-9'`. The probes pass it as -P "$HEALTH_PASSWORD" inside
the container, so it is never parsed by a URL or config file. That must
be re-checked per user before enrolling sml2mqtt/open3e (edge-host
templates), shellies (device firmware) or homeassistant.
- OpenBao path already holds a `password` field, and its value still
matches the live Secret (both 23c10b3b), so ESO adoption on merge is a
no-op and no client sees an auth change.
- eso-read.hcl already grants homelab/data/*, so no ESO policy change.
- Policy and kubernetes auth role applied out-of-band (they are not
Kubernetes resources): bound to SA mosquitto-health-secret-rotator in ns
openbao, ttl 15m.
Not done here: the first actual rotation. It is safe (in-cluster only) but
restarts the broker, so it is left as a deliberate post-merge acceptance
test rather than bundled into a merge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖Automated review — model CLI default (complexity: complex), commit 91133c94b044cc2d2827b9a3db901da15f2927e5.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
Removes the health broker-user bootstrap Job and replaces it with an OpenBao-backed, Class-A-rotated credential (ExternalSecret + per-secret OpenBao policy + quarterly rotation CronJob + a Reloader annotation on the mosquitto Deployment), following the existing paperless rotation pattern. I cross-checked the new manifests against rotation/paperless.yaml, rotator-paperless.hcl, eso-read.hcl, the openbaoClusterSecretStore, mosquitto-deployment.yaml, and the secret-rotator Rust source (random mode, assert_fields_exist, min-16-byte guard). Everything is internally consistent — paths, field names, role/SA names, the cron offset from paperless, and the six remaining bootstrap Jobs all line up with what the PR description claims. No functional bugs found.
No blocking issues.
🟡 Suggestions
Dangling doc reference to a file that doesn't exist.mosquitto-health-credentials-externalsecret.yaml:125-126, rotation/mosquitto-health.yaml:224-225, and policies/rotator-mosquitto-health.hcl:173-174 all point to kubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md as the design doc for this rotation effort. That file is not present anywhere in the repo (checked working tree and git log --all). Anyone following the reference — including whoever enrolls broker user #2 — will hit a 404. Either add the doc in this PR (or a companion one) or point the comments at wherever the actual plan lives (e.g. issue #453, linked in the PR body).
🟢 Nits
mosquitto-deployment.yaml:192,202,245 — pre-existing comments describe the HEALTH_PASSWORD and ZIGBEE2MQTT_PASSWORD env vars/probes as coming from "mosquitto-health-credentials-bootstrap" (a Job). This PR deletes that Job, so the comments now describe infrastructure that no longer exists. Not touched by this diff, but worth a follow-up cleanup since this PR is the one that made them stale.
Tests
No automated tests apply to this class of change (matches the existing convention — rotation/paperless.yaml and its policy have none either). The PR body documents manual verification instead: kustomize build + server-side dry-run on both kustomizations, and out-of-band confirmation that the OpenBao path already holds a matching password value. I couldn't run kustomize/kubectl myself in this sandbox, but manual review of both kustomization.yaml changes (home-assistant app, openbao rotation) confirms the added resource entries point at files that exist and are well-formed, and the bootstrap file still contains exactly six Job blocks post-deletion, matching the PR's claim. The one thing that is explicitly not tested here — the actual rotation round-trip (rotator → OpenBao → ESO → Reloader → broker) — is called out by the author as a deliberate post-merge acceptance step, which is a reasonable call given the brief probe-auth blip it can cause.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `CLI default` (complexity: complex), commit `91133c94b044cc2d2827b9a3db901da15f2927e5`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
Removes the `health` broker-user bootstrap Job and replaces it with an OpenBao-backed, Class-A-rotated credential (ExternalSecret + per-secret OpenBao policy + quarterly rotation CronJob + a Reloader annotation on the mosquitto Deployment), following the existing `paperless` rotation pattern. I cross-checked the new manifests against `rotation/paperless.yaml`, `rotator-paperless.hcl`, `eso-read.hcl`, the `openbao` `ClusterSecretStore`, `mosquitto-deployment.yaml`, and the `secret-rotator` Rust source (`random` mode, `assert_fields_exist`, min-16-byte guard). Everything is internally consistent — paths, field names, role/SA names, the cron offset from paperless, and the six remaining bootstrap Jobs all line up with what the PR description claims. No functional bugs found.
No blocking issues.
## 🟡 Suggestions
- **Dangling doc reference to a file that doesn't exist.** `mosquitto-health-credentials-externalsecret.yaml:125-126`, `rotation/mosquitto-health.yaml:224-225`, and `policies/rotator-mosquitto-health.hcl:173-174` all point to `kubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md` as the design doc for this rotation effort. That file is not present anywhere in the repo (checked working tree and `git log --all`). Anyone following the reference — including whoever enrolls broker user #2 — will hit a 404. Either add the doc in this PR (or a companion one) or point the comments at wherever the actual plan lives (e.g. issue #453, linked in the PR body).
## 🟢 Nits
- `mosquitto-deployment.yaml:192,202,245` — pre-existing comments describe the `HEALTH_PASSWORD` and `ZIGBEE2MQTT_PASSWORD` env vars/probes as coming from "`mosquitto-health-credentials-bootstrap`" (a Job). This PR deletes that Job, so the comments now describe infrastructure that no longer exists. Not touched by this diff, but worth a follow-up cleanup since this PR is the one that made them stale.
## Tests
No automated tests apply to this class of change (matches the existing convention — `rotation/paperless.yaml` and its policy have none either). The PR body documents manual verification instead: `kustomize build` + server-side dry-run on both kustomizations, and out-of-band confirmation that the OpenBao path already holds a matching `password` value. I couldn't run `kustomize`/`kubectl` myself in this sandbox, but manual review of both `kustomization.yaml` changes (home-assistant app, openbao rotation) confirms the added resource entries point at files that exist and are well-formed, and the bootstrap file still contains exactly six `Job` blocks post-deletion, matching the PR's claim. The one thing that is explicitly *not* tested here — the actual rotation round-trip (rotator → OpenBao → ESO → Reloader → broker) — is called out by the author as a deliberate post-merge acceptance step, which is a reasonable call given the brief probe-auth blip it can cause.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=91133c94b044cc2d2827b9a3db901da15f2927e5 -->
Review points on #455.
The three new files referenced PROPOSAL-mosquitto-credential-rotation.md
as though it were in the tree. It is not — it lives in PR #453, which is
still open, so anyone following the reference (including whoever enrols
broker user #2) would hit a 404. That is a sequencing mistake on my part:
I wrote the implementation against a doc that had not landed. The
references now say so explicitly rather than silently pointing at nothing.
Also fixed two comments in mosquitto-deployment.yaml that described
HEALTH_PASSWORD as coming from the mosquitto-health-credentials-bootstrap
Job. This PR deletes that Job, so those comments described infrastructure
that no longer exists. They now point at the ExternalSecret. Fixing here
rather than deferring, since this PR is what made them stale.
Deliberately did NOT touch the neighbouring zigbee2mqtt comment the review
also flagged: that Job still exists (only health is enrolled in this
phase), so the reference is still accurate. It becomes stale when
zigbee2mqtt is enrolled, and should be fixed by that change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Correct, and it is a sequencing mistake on my part: I wrote the implementation referencing a design doc that has not landed. PROPOSAL-mosquitto-credential-rotation.md lives in #453, still open, so all three references pointed at nothing on master. Whoever enrols broker user #2 would have followed it into a 404.
The references now state explicitly that the file arrives with #453 and does not resolve until it merges. Either merge order works — this PR no longer implies the doc is already there.
🟢 Stale bootstrap comments
Fixed here rather than deferred, since this PR is what made them stale. Both HEALTH_PASSWORD comments now point at the ExternalSecret instead of the deleted Job.
One correction to the review: of the three lines flagged, only two were stale. The third (mosquitto-zigbee2mqtt-credentials-bootstrap) is still accurate — that Job still exists, because only health is enrolled in this phase. It becomes stale when zigbee2mqtt is enrolled, and belongs to that change. Left alone deliberately rather than pre-emptively rewritten to describe something that is not true yet.
Unchanged
Still kustomize build + server-side dry-run clean on both kustomizations, six Jobs remaining in mosquitto-bootstrap.yaml, and the OpenBao value still matching the live Secret so merging remains a no-op for the broker.
Both addressed in `2d24d0b6`.
### 🟡 Dangling doc reference
Correct, and it is a sequencing mistake on my part: I wrote the implementation referencing a design doc that has not landed. `PROPOSAL-mosquitto-credential-rotation.md` lives in **#453, still open**, so all three references pointed at nothing on master. Whoever enrols broker user #2 would have followed it into a 404.
The references now state explicitly that the file arrives with #453 and does not resolve until it merges. Either merge order works — this PR no longer implies the doc is already there.
### 🟢 Stale bootstrap comments
Fixed here rather than deferred, since this PR is what made them stale. Both `HEALTH_PASSWORD` comments now point at the ExternalSecret instead of the deleted Job.
One correction to the review: of the three lines flagged, only **two** were stale. The third (`mosquitto-zigbee2mqtt-credentials-bootstrap`) is still accurate — that Job still exists, because only `health` is enrolled in this phase. It becomes stale when zigbee2mqtt is enrolled, and belongs to that change. Left alone deliberately rather than pre-emptively rewritten to describe something that is not true yet.
### Unchanged
Still `kustomize build` + server-side dry-run clean on both kustomizations, six Jobs remaining in mosquitto-bootstrap.yaml, and the OpenBao value still matching the live Secret so merging remains a no-op for the broker.
🤖Automated review — model CLI default (complexity: complex), commit 2d24d0b6d1d033b9887d37f1765cb9d7f082f49d.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
Migrates the mosquitto health broker credential from a one-shot bootstrap Job to OpenBao-backed Class-A rotation (per-secret policy, quarterly CronJob, ExternalSecret, Reloader annotation), following the existing rotation/paperless.yaml template closely. I compared every new/changed file against the established patterns in this repo (paperless, evcc, home-assistant rotators) and everything lines up — env contract, security context, cron offsetting, HCL policy structure, and the Reloader annotation syntax all match. No blocking issues found.
🟡 Suggestions
kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl: the other recently-added rotator policies that use bao kv patch (rotator-paperless.hcl:22, rotator-claude-oauth.hcl, rotator-review-bot.hcl) all carry the line Do NOT set token_no_default_policy=true — the kv-v2 preflight needs the default policy. This new file omits it. Since these are applied manually and out-of-band (not reconciled by Flux), the warning exists specifically to stop someone from copy-pasting a hardened bao write auth/kubernetes/role/... command that breaks the bao kv patch preflight the rotator depends on. Worth adding for consistency before someone applies this role by hand.
Tests
No automated tests apply to this class of change (Kubernetes manifests + OpenBao HCL policy, applied via Flux/manual bao commands). The PR body documents the equivalent verification: kustomize build + server-side dry-run on both kustomizations, and out-of-band confirmation that the OpenBao policy/role/seed value are correct and readable. That matches how other rotators in this repo were validated (e.g. paperless). The PR explicitly and reasonably defers the actual first rotation (which restarts the broker) to a documented post-merge on-demand Job rather than bundling it into the merge — a sound way to keep this change reversible and low-risk.
I also checked for the exact issue this PR's own last commit says it was fixing (a dangling reference to PROPOSAL-mosquitto-credential-rotation.md, which lives in unmerged PR #453, plus stale comments pointing at the now-deleted bootstrap Job): all three references to the proposal doc are now correctly caveated as "PR #453 — not yet merged," and a repo-wide grep for the deleted Job's name (mosquitto-health-credentials-bootstrap) turns up nothing — the fixup is complete and consistent.
Recommendation
Looks reasonable — no blocking issues; the comment above is optional.
🤖 **Automated review** — model `CLI default` (complexity: complex), commit `2d24d0b6d1d033b9887d37f1765cb9d7f082f49d`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
Migrates the mosquitto `health` broker credential from a one-shot bootstrap Job to OpenBao-backed Class-A rotation (per-secret policy, quarterly CronJob, ExternalSecret, Reloader annotation), following the existing `rotation/paperless.yaml` template closely. I compared every new/changed file against the established patterns in this repo (paperless, evcc, home-assistant rotators) and everything lines up — env contract, security context, cron offsetting, HCL policy structure, and the Reloader annotation syntax all match. No blocking issues found.
## 🟡 Suggestions
- `kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl`: the other recently-added rotator policies that use `bao kv patch` (`rotator-paperless.hcl:22`, `rotator-claude-oauth.hcl`, `rotator-review-bot.hcl`) all carry the line `Do NOT set token_no_default_policy=true — the kv-v2 preflight needs the default policy`. This new file omits it. Since these are applied manually and out-of-band (not reconciled by Flux), the warning exists specifically to stop someone from copy-pasting a hardened `bao write auth/kubernetes/role/...` command that breaks the `bao kv patch` preflight the rotator depends on. Worth adding for consistency before someone applies this role by hand.
## Tests
No automated tests apply to this class of change (Kubernetes manifests + OpenBao HCL policy, applied via Flux/manual bao commands). The PR body documents the equivalent verification: `kustomize build` + server-side dry-run on both kustomizations, and out-of-band confirmation that the OpenBao policy/role/seed value are correct and readable. That matches how other rotators in this repo were validated (e.g. paperless). The PR explicitly and reasonably defers the actual first rotation (which restarts the broker) to a documented post-merge on-demand Job rather than bundling it into the merge — a sound way to keep this change reversible and low-risk.
I also checked for the exact issue this PR's own last commit says it was fixing (a dangling reference to `PROPOSAL-mosquitto-credential-rotation.md`, which lives in unmerged PR #453, plus stale comments pointing at the now-deleted bootstrap Job): all three references to the proposal doc are now correctly caveated as "PR #453 — not yet merged," and a repo-wide grep for the deleted Job's name (`mosquitto-health-credentials-bootstrap`) turns up nothing — the fixup is complete and consistent.
## Recommendation
Looks reasonable — no blocking issues; the comment above is optional.
<!-- pr-review:sha=2d24d0b6d1d033b9887d37f1765cb9d7f082f49d -->
Added in the latest push. Verified both halves of the claim rather than just copying the line:
Five sibling policies carry it (rotator-paperless, rotator-authentik, rotator-cloudflare, rotator-external-dns, rotator-gitea-flux), so it is a convention and mine was the outlier.
The live role is already correct — token_no_default_policy: false — so this documents an existing good state rather than fixing a broken one.
The reason it is worth having is exactly the one you give: these are applied by hand, out of band. Without the warning, this file is the one that looks safe to harden, and the failure mode is a rotation that stops working silently rather than an obvious error. I extended the wording slightly to say why (the rotator writes via patch, so hardening the role that way would break rotation rather than tighten it) so the next person does not have to chase rotator.hcl to find out.
Thanks also for independently confirming the previous fixup was complete — the repo-wide grep for the deleted Job name turning up nothing is a better check than the one I ran.
Added in the latest push. Verified both halves of the claim rather than just copying the line:
- **Five** sibling policies carry it (`rotator-paperless`, `rotator-authentik`, `rotator-cloudflare`, `rotator-external-dns`, `rotator-gitea-flux`), so it is a convention and mine was the outlier.
- The **live role is already correct** — `token_no_default_policy: false` — so this documents an existing good state rather than fixing a broken one.
The reason it is worth having is exactly the one you give: these are applied by hand, out of band. Without the warning, this file is the one that *looks* safe to harden, and the failure mode is a rotation that stops working silently rather than an obvious error. I extended the wording slightly to say why (`the rotator writes via patch, so hardening the role that way would break rotation rather than tighten it`) so the next person does not have to chase `rotator.hcl` to find out.
Thanks also for independently confirming the previous fixup was complete — the repo-wide grep for the deleted Job name turning up nothing is a better check than the one I ran.
Review point on #455. Five sibling rotator policies carry this line and
mine omitted it:
Do NOT set token_no_default_policy=true — the `bao kv patch` kv-v2
preflight needs the default policy (see rotator.hcl).
It matters precisely because these policies are applied BY HAND, out of
band: the warning exists to stop someone tightening the role in a later
hardening pass and silently breaking the patch the rotator depends on.
Omitting it made this file the one that looks safe to harden.
Verified the live role is already correct — token_no_default_policy is
false — so this documents an existing good state rather than fixing a
broken one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖Automated review — model CLI default (complexity: complex), commit 0817aaa24b72749c1aebacc85362c9e18a35dd46.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
Migrates the mosquitto health broker credential (probe-only, used only by liveness/readiness exec probes inside the mosquitto container) from a one-shot bootstrap Job onto the repo's existing Class-A OpenBao rotation pattern (rotator CronJob + per-secret policy + ExternalSecret + Reloader annotation). It follows rotation/paperless.yaml closely and I checked it against that template, the ESO/OpenBao path conventions, mosquitto-deployment.yaml, and the remaining bootstrap Jobs. Everything lines up; I found no blocking issues.
🟡 Suggestions
kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl:214 — references "see rotator.hcl", but no file by that name exists in kubernetes/infrastructure/openbao/policies/ (only rotator-<app>.hcl per-app files and eso-read.hcl). This is copied verbatim from rotator-paperless.hcl:22, so it's a pre-existing dangling reference, not something this PR introduces — but since this PR is establishing the copy-this-file template for the other seven users, it might be worth fixing the source (rotator-paperless.hcl) so the reference doesn't keep propagating.
The rotator image tag (secret-rotator@sha256:159e5888...) is reused byte-for-byte from rotation/paperless.yaml, while several other rotation files in the same directory (home-assistant.yaml, claude-oauth.yaml, review-bot.yaml, woodpecker.yaml) pin different, presumably newer, digests. I couldn't confirm the chronology/changelog between these digests from the diff alone (git-log access was unavailable in this review environment), so I can't say whether the paperless-era image is missing a fix present in the newer ones — worth a quick check that random mode behaves identically across the digests in use before assuming parity.
Tests
No automated tests apply to this class of change (Kustomize/OpenBao manifests) and the repo doesn't appear to have any for the sibling rotators either, so this is consistent with existing practice. The PR body explicitly and reasonably defers the one meaningful "test" — actually triggering mosquitto-health-secret-rotate-now and observing the OpenBao value change, ESO sync, Reloader roll, and probes passing — to a post-merge manual step, since it restarts the single-replica broker. That's a sound call for a manifests-only PR; I did not execute it.
Static checks I performed in place of runtime tests:
Confirmed policies/rotator-mosquitto-health.hcl's path (homelab/data/apps/home-assistant/mosquitto-health-credentials) matches the rotator's BAO_PATH (homelab/apps/home-assistant/mosquitto-health-credentials) under the data/ KV-v2 convention used by rotator-paperless.hcl.
Confirmed the ExternalSecret's dataFrom key (apps/home-assistant/mosquitto-health-credentials) matches the sibling mosquitto-evcc-credentials-externalsecret.yaml convention, and that secretStoreRef (ClusterSecretStore/openbao) exists at kubernetes/infrastructure/post-crds/openbao-clustersecretstore.yaml.
Confirmed the CronJob schedule (0 4 1 2,5,8,11 *) is quarterly, genuinely offset one month from paperless's (0 4 1 1,4,7,10 *), as claimed.
Confirmed removing only the mosquitto-health-credentials-bootstrap Job from mosquitto-bootstrap.yaml leaves the diff's --- document separators intact and doesn't orphan the shared mosquitto-bootstrap ServiceAccount, which is still used by the six remaining Jobs.
Confirmed no NetworkPolicy exists under kubernetes/infrastructure/openbao/ that would block the new rotator Pod from reaching the in-namespace OpenBao service, matching the PR's "no NetworkPolicy needed" claim.
Confirmed mosquitto-deployment.yaml's secretKeyRef for HEALTH_PASSWORD (mosquitto-health-credentials / password) is unchanged and matches the new ExternalSecret's target.name and key, so the probes and passwd-file build keep working untouched.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `CLI default` (complexity: complex), commit `0817aaa24b72749c1aebacc85362c9e18a35dd46`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
Migrates the mosquitto `health` broker credential (probe-only, used only by liveness/readiness exec probes inside the mosquitto container) from a one-shot bootstrap Job onto the repo's existing Class-A OpenBao rotation pattern (rotator CronJob + per-secret policy + ExternalSecret + Reloader annotation). It follows `rotation/paperless.yaml` closely and I checked it against that template, the ESO/OpenBao path conventions, `mosquitto-deployment.yaml`, and the remaining bootstrap Jobs. Everything lines up; I found no blocking issues.
## 🟡 Suggestions
- `kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl:214` — references "see rotator.hcl", but no file by that name exists in `kubernetes/infrastructure/openbao/policies/` (only `rotator-<app>.hcl` per-app files and `eso-read.hcl`). This is copied verbatim from `rotator-paperless.hcl:22`, so it's a pre-existing dangling reference, not something this PR introduces — but since this PR is establishing the copy-this-file template for the other seven users, it might be worth fixing the source (`rotator-paperless.hcl`) so the reference doesn't keep propagating.
- The rotator image tag (`secret-rotator@sha256:159e5888...`) is reused byte-for-byte from `rotation/paperless.yaml`, while several other rotation files in the same directory (`home-assistant.yaml`, `claude-oauth.yaml`, `review-bot.yaml`, `woodpecker.yaml`) pin different, presumably newer, digests. I couldn't confirm the chronology/changelog between these digests from the diff alone (git-log access was unavailable in this review environment), so I can't say whether the paperless-era image is missing a fix present in the newer ones — worth a quick check that `random` mode behaves identically across the digests in use before assuming parity.
## Tests
No automated tests apply to this class of change (Kustomize/OpenBao manifests) and the repo doesn't appear to have any for the sibling rotators either, so this is consistent with existing practice. The PR body explicitly and reasonably defers the one meaningful "test" — actually triggering `mosquitto-health-secret-rotate-now` and observing the OpenBao value change, ESO sync, Reloader roll, and probes passing — to a post-merge manual step, since it restarts the single-replica broker. That's a sound call for a manifests-only PR; I did not execute it.
Static checks I performed in place of runtime tests:
- Confirmed `policies/rotator-mosquitto-health.hcl`'s path (`homelab/data/apps/home-assistant/mosquitto-health-credentials`) matches the rotator's `BAO_PATH` (`homelab/apps/home-assistant/mosquitto-health-credentials`) under the `data/` KV-v2 convention used by `rotator-paperless.hcl`.
- Confirmed the ExternalSecret's `dataFrom` key (`apps/home-assistant/mosquitto-health-credentials`) matches the sibling `mosquitto-evcc-credentials-externalsecret.yaml` convention, and that `secretStoreRef` (`ClusterSecretStore/openbao`) exists at `kubernetes/infrastructure/post-crds/openbao-clustersecretstore.yaml`.
- Confirmed the CronJob schedule (`0 4 1 2,5,8,11 *`) is quarterly, genuinely offset one month from paperless's (`0 4 1 1,4,7,10 *`), as claimed.
- Confirmed removing only the `mosquitto-health-credentials-bootstrap` Job from `mosquitto-bootstrap.yaml` leaves the diff's `---` document separators intact and doesn't orphan the shared `mosquitto-bootstrap` ServiceAccount, which is still used by the six remaining Jobs.
- Confirmed no `NetworkPolicy` exists under `kubernetes/infrastructure/openbao/` that would block the new rotator Pod from reaching the in-namespace OpenBao service, matching the PR's "no NetworkPolicy needed" claim.
- Confirmed `mosquitto-deployment.yaml`'s `secretKeyRef` for `HEALTH_PASSWORD` (`mosquitto-health-credentials` / `password`) is unchanged and matches the new ExternalSecret's `target.name` and key, so the probes and passwd-file build keep working untouched.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=0817aaa24b72749c1aebacc85362c9e18a35dd46 -->
The review asked whether copying paperless's rotator digest was safe.
Checking it turned up something worse than a version-skew question: that
digest does not exist in the registry at all.
sha256:159e5888… ImagePullBackOff / NotFound
Verified by pulling it, not by inspecting metadata. So this PR as written
would have shipped a CronJob that fails on its first run in three months'
time — silently, since a quarterly rotation failing looks like nothing
happening.
Now pinned to the digest :latest currently resolves to
(sha256:dcf4267a…), verified to pull, to list `random` among its modes,
and to carry the RANDOM_BYTES>=16 floor this file documents (RANDOM_BYTES=8
exits with "refusing to generate a weak secret" rather than generating one).
BROADER PROBLEM, not introduced here and not fixed here: 11 of the 14
existing rotators are pinned to digests that are equally gone. Tested each:
gone 159e5888 authentik, crowdsec-bouncer-key, paperless,
shelly-manager, woodpecker, home-assistant
gone 978fe53c cloudflare, external-dns
gone 49bfddd8 netbird-ingress
gone 97c22bcf pve-exporter
present b41cc364 flux-git
present 828bf486 claude-oauth, review-bot
The two surviving digests belong to the rotators that run often; the gone
ones are all quarterly or infrequent. Untagged manifests appear to be
garbage-collected once :latest and the dated tag move past them, so a
digest pinned today stops resolving after a couple of rebuilds. Every
affected rotator will ImagePullBackOff on its next scheduled run unless the
image happens to still be cached on whichever node it lands on.
That means secret rotation across most of this cluster is probably already
broken and would fail quietly. Raised separately rather than fixed here —
it needs a decision (repin all, stop GC'ing, or tag-pin rather than
digest-pin), not a drive-by edit inside a mosquitto PR.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖Automated review — model CLI default (complexity: complex), commit 4df89506fba15ddff24653b41b576676984bf7fc.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
This enrolls the mosquitto health broker credential in the OpenBao-backed "Class-A" rotation pattern already used by paperless/authentik/woodpecker/etc.: new per-secret OpenBao policy, a quarterly rotation CronJob, an ExternalSecret projecting back to the existing Secret name/key, a Reloader annotation on the Deployment, and removal of the now-redundant bootstrap Job. I checked every factual claim in the PR description against the actual manifests and the secret-rotator Rust source, and all of them held up: the random mode really does call assert_fields_exist before patching (never bootstraps a field), random_secret really does use standard (padded) base64 with +///=, the RANDOM_BYTES<16 floor exists and rejects weak secrets, the schedule is genuinely offset one month from paperless's, home-assistant is already an allowed namespace on the openbao ClusterSecretStore, and the six remaining bootstrap Jobs plus the removed health Job diff match what's described. No blocking issues found.
🟡 Suggestions
PR description overstates how many users still have bootstrap Jobs. The body says "One of eight… the other seven are untouched and keep their bootstrap Jobs," but mosquitto-bootstrap.yaml already has no Job for evcc in master (its mosquitto-evcc-credentials-externalsecret.yaml predates this PR and isn't part of this diff). So only six of the eight users still use bootstrap Jobs after this merges, not seven — evcc was already moved off that pattern (via ExternalSecret) before this PR, just without a rotation CronJob. Doesn't affect the shipped manifests, but worth correcting in the PR body/commit message so #453's phase count doesn't drift from reality.
Cross-PR doc reference won't resolve if merge order flips.mosquitto-health-credentials-externalsecret.yaml:145, rotator-mosquitto-health.hcl:198, and mosquitto-health.yaml:253 all point at kubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md, which doesn't exist in this checkout (confirmed via find — it's added by #453, not yet merged). This is disclosed in-comment, so not a surprise, but if #455 merges to master before #453 lands, those references stay dead for whatever window separates the two merges. Worth merging #453 first, or squash-ordering them, if that isn't already the plan.
🟢 Nits
rotator-mosquitto-health.hcl:214 ("Do NOT set token_no_default_policy=true... see rotator.hcl") points at a file that doesn't exist in this repo (kubernetes/infrastructure/openbao/policies/rotator.hcl) — but this is copied verbatim from rotator-paperless.hcl, which has the identical dangling reference, so it's a pre-existing issue in the template, not something this PR introduces.
Tests
No automated tests exist for this class of change (OpenBao policies/CronJobs aren't Kubernetes-native-tested elsewhere in the repo either), consistent with every prior rotator PR. The PR body documents manual verification performed before commit (kustomize build + server-side dry run, live check that the new image digest resolves and random is a supported mode, live check that the OpenBao value already matches the Secret). The actual first rotation is deliberately deferred to a post-merge on-demand kubectl create job run rather than exercised here — reasonable given it restarts the broker, and the PR is explicit that this is intentionally left undone.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `CLI default` (complexity: complex), commit `4df89506fba15ddff24653b41b576676984bf7fc`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
This enrolls the mosquitto `health` broker credential in the OpenBao-backed "Class-A" rotation pattern already used by paperless/authentik/woodpecker/etc.: new per-secret OpenBao policy, a quarterly rotation CronJob, an ExternalSecret projecting back to the existing Secret name/key, a Reloader annotation on the Deployment, and removal of the now-redundant bootstrap Job. I checked every factual claim in the PR description against the actual manifests and the `secret-rotator` Rust source, and all of them held up: the `random` mode really does call `assert_fields_exist` before patching (never bootstraps a field), `random_secret` really does use standard (padded) base64 with `+`/`/`/`=`, the `RANDOM_BYTES<16` floor exists and rejects weak secrets, the schedule is genuinely offset one month from paperless's, `home-assistant` is already an allowed namespace on the `openbao` ClusterSecretStore, and the six remaining bootstrap Jobs plus the removed `health` Job diff match what's described. No blocking issues found.
## 🟡 Suggestions
- **PR description overstates how many users still have bootstrap Jobs.** The body says "One of eight… the other seven are untouched and keep their bootstrap Jobs," but `mosquitto-bootstrap.yaml` already has no Job for `evcc` in `master` (its `mosquitto-evcc-credentials-externalsecret.yaml` predates this PR and isn't part of this diff). So only **six** of the eight users still use bootstrap Jobs after this merges, not seven — `evcc` was already moved off that pattern (via ExternalSecret) before this PR, just without a rotation CronJob. Doesn't affect the shipped manifests, but worth correcting in the PR body/commit message so `#453`'s phase count doesn't drift from reality.
- **Cross-PR doc reference won't resolve if merge order flips.** `mosquitto-health-credentials-externalsecret.yaml:145`, `rotator-mosquitto-health.hcl:198`, and `mosquitto-health.yaml:253` all point at `kubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md`, which doesn't exist in this checkout (confirmed via `find` — it's added by #453, not yet merged). This is disclosed in-comment, so not a surprise, but if #455 merges to master before #453 lands, those references stay dead for whatever window separates the two merges. Worth merging #453 first, or squash-ordering them, if that isn't already the plan.
## 🟢 Nits
- `rotator-mosquitto-health.hcl:214` ("Do NOT set `token_no_default_policy=true`... see `rotator.hcl`") points at a file that doesn't exist in this repo (`kubernetes/infrastructure/openbao/policies/rotator.hcl`) — but this is copied verbatim from `rotator-paperless.hcl`, which has the identical dangling reference, so it's a pre-existing issue in the template, not something this PR introduces.
## Tests
No automated tests exist for this class of change (OpenBao policies/CronJobs aren't Kubernetes-native-tested elsewhere in the repo either), consistent with every prior rotator PR. The PR body documents manual verification performed before commit (kustomize build + server-side dry run, live check that the new image digest resolves and `random` is a supported mode, live check that the OpenBao value already matches the Secret). The actual first rotation is deliberately deferred to a post-merge on-demand `kubectl create job` run rather than exercised here — reasonable given it restarts the broker, and the PR is explicit that this is intentionally left undone.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=4df89506fba15ddff24653b41b576676984bf7fc -->
Owner directive: our own images do not need digest pinning, and :latest is
never to be used. Dated tag it is — matching ci-ansible:2026-04-29 and the
alpine-jq / cni-plugins / alpine-openssl pins added earlier today.
Now secret-rotator:2026-07-30. Verified before pinning that the tag exists,
pulls, lists `random` among its modes, and carries the RANDOM_BYTES>=16
floor this file documents (RANDOM_BYTES=8 exits "refusing to generate a
weak secret" rather than generating one).
Worth recording why the tag is today's rather than 2026-07-23, the date of
the last commit touching secret-rotator/: the build step also triggers on
.woodpecker/build-ci-images.yml, which was edited several times today while
adding the alpine-jq, cni-plugins and alpine-openssl images. So
secret-rotator was rebuilt and republished today from unchanged source.
2026-07-23 resolves to a different, older image (sha256:828bf486…) that is
still present; 2026-07-30 matches what :latest currently points at.
That incidental rebuild is also part of why the digest pins decayed: every
unrelated pipeline edit moves :latest and orphans the previous manifest,
which then gets garbage-collected. Tracked in #458 for the other 11
rotators; this change keeps the new one out of that trap rather than
joining it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖Automated review — model CLI default (complexity: complex), commit 9e75ab6b4d4fef96aece7f714618199262f810ac.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
Phase 1 of the mosquitto credential-rotation migration (#453): moves the broker's health user off its one-shot bootstrap Job and onto OpenBao-backed Class-A rotation (ExternalSecret + per-secret rotator policy + CronJob), with a Reloader annotation so rotation actually takes effect. I checked every claim in the PR body against the checkout — the Secret name/key, ExternalSecret path convention, OpenBao policy path, and the reload/consumption wiring in mosquitto-deployment.yaml are all internally consistent and match the sibling mosquitto-evcc-credentials / rotator-paperless patterns exactly. No blocking issues found.
🟡 Suggestions
kubernetes/infrastructure/openbao/rotation/mosquitto-health.yaml:352 — the image is pinned by dated tag (secret-rotator:2026-07-30) rather than digest, explicitly diverging from every sibling rotator (paperless.yaml:66, woodpecker.yaml:66, etc., all @sha256:...). The stated reason (those digests were garbage-collected and would ImagePullBackOff, tracked as #458) is plausible and matches this repo's evidence — no non-:latest/digest-pinned rotator exists elsewhere — but I have no way to independently confirm from the checkout that the referenced digests are actually gone from the registry (no network access here). Worth having one other set of eyes confirm before merge, since it sets a precedent other rotators may want to follow once #458 is addressed.
kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl:198 and kubernetes/infrastructure/openbao/rotation/mosquitto-health.yaml:253 reference kubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md, which does not exist in this checkout (confirmed via glob) since it ships in the not-yet-merged #453. This is called out explicitly in both places, so it's not misleading, just a currently-dead link — fine as long as #453 lands before/with this.
Tests
No automated tests apply here (this is GitOps YAML + an out-of-band OpenBao policy/role, consistent with every other rotator in the repo — none have tests). The PR body documents kustomize build + server-side dry-run having been run on both kustomizations, and defers the actual first rotation to a documented post-merge manual step (kubectl create job --from=cronjob/...) rather than bundling a broker restart into the merge — reasonable given the blast radius is a live broker roll.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `CLI default` (complexity: complex), commit `9e75ab6b4d4fef96aece7f714618199262f810ac`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
Phase 1 of the mosquitto credential-rotation migration (#453): moves the broker's `health` user off its one-shot bootstrap Job and onto OpenBao-backed Class-A rotation (ExternalSecret + per-secret rotator policy + CronJob), with a Reloader annotation so rotation actually takes effect. I checked every claim in the PR body against the checkout — the Secret name/key, ExternalSecret path convention, OpenBao policy path, and the reload/consumption wiring in `mosquitto-deployment.yaml` are all internally consistent and match the sibling `mosquitto-evcc-credentials` / `rotator-paperless` patterns exactly. No blocking issues found.
## 🟡 Suggestions
- `kubernetes/infrastructure/openbao/rotation/mosquitto-health.yaml:352` — the image is pinned by dated tag (`secret-rotator:2026-07-30`) rather than digest, explicitly diverging from every sibling rotator (`paperless.yaml:66`, `woodpecker.yaml:66`, etc., all `@sha256:...`). The stated reason (those digests were garbage-collected and would ImagePullBackOff, tracked as #458) is plausible and matches this repo's evidence — no non-`:latest`/digest-pinned rotator exists elsewhere — but I have no way to independently confirm from the checkout that the referenced digests are actually gone from the registry (no network access here). Worth having one other set of eyes confirm before merge, since it sets a precedent other rotators may want to follow once #458 is addressed.
- `kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl:198` and `kubernetes/infrastructure/openbao/rotation/mosquitto-health.yaml:253` reference `kubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md`, which does not exist in this checkout (confirmed via glob) since it ships in the not-yet-merged #453. This is called out explicitly in both places, so it's not misleading, just a currently-dead link — fine as long as #453 lands before/with this.
## Tests
No automated tests apply here (this is GitOps YAML + an out-of-band OpenBao policy/role, consistent with every other rotator in the repo — none have tests). The PR body documents `kustomize build` + server-side dry-run having been run on both kustomizations, and defers the actual first rotation to a documented post-merge manual step (`kubectl create job --from=cronjob/...`) rather than bundling a broker restart into the merge — reasonable given the blast radius is a live broker roll.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=9e75ab6b4d4fef96aece7f714618199262f810ac -->
Both fair. Neither needs a code change, but the first deserves evidence rather than my say-so.
🟡 Dated tag diverging from every sibling
You are right to flag it and right that you could not confirm it from the checkout. Two things settle it:
1. It is now an owner directive, not my preference. Stated during this work: our own images do not need digest pinning, and :latest is never to be used. So dated tags are the intended convention for registry-local images, matching ci-ansible:2026-04-29 already in the repo and the three images added today (alpine-jq, cni-plugins, alpine-openssl, all :2026-07-30). This PR is not setting a precedent unilaterally — it is the first rotator to follow one that already exists elsewhere.
2. The digests really are gone. Verified by pulling each in-cluster, since the registry API returns 401 for a PAT and metadata checks would have been misleading:
The control is the two that DO pull: both belong to rotators that run frequently (claude-oauth-secret-sync is daily and ran 12h ago). Every gone digest belongs to a quarterly one. So this is not a broken registry — it is untagged manifests being garbage-collected as tags move on.
And the mechanism is worse than it first looks: secret-rotator was rebuilt and republished today from unchanged source, because the build step also triggers on .woodpecker/build-ci-images.yml, which I edited while adding three unrelated images. So digest pins decay on a cadence driven by edits to a shared file that has nothing to do with the pinned image. Copying the sibling convention would have shipped a CronJob broken from the day it was written — it just would not have surfaced until 2026-10-01.
Full detail and the remediation for the other 11 in #458.
🟡 Dead link to the proposal doc
Correct, and the fix is merge order rather than an edit: #453 should land before or with this. Both references say so explicitly so the link is never silently wrong, but the cleanest sequence is #453 then #455.
Both fair. Neither needs a code change, but the first deserves evidence rather than my say-so.
### 🟡 Dated tag diverging from every sibling
You are right to flag it and right that you could not confirm it from the checkout. Two things settle it:
**1. It is now an owner directive, not my preference.** Stated during this work: *our own images do not need digest pinning, and `:latest` is never to be used.* So dated tags are the intended convention for registry-local images, matching `ci-ansible:2026-04-29` already in the repo and the three images added today (`alpine-jq`, `cni-plugins`, `alpine-openssl`, all `:2026-07-30`). This PR is not setting a precedent unilaterally — it is the first rotator to follow one that already exists elsewhere.
**2. The digests really are gone.** Verified by pulling each in-cluster, since the registry API returns 401 for a PAT and metadata checks would have been misleading:
```
sha256:159e5888… ImagePullBackOff / NotFound authentik, crowdsec, paperless,
shelly-manager, woodpecker, home-assistant
sha256:978fe53c… ImagePullBackOff / NotFound cloudflare, external-dns
sha256:49bfddd8… ImagePullBackOff / NotFound netbird-ingress
sha256:97c22bcf… ImagePullBackOff / NotFound pve-exporter
sha256:b41cc364… pulled + ran flux-git
sha256:828bf486… pulled + ran claude-oauth, review-bot
```
The control is the two that DO pull: both belong to rotators that run frequently (`claude-oauth-secret-sync` is daily and ran 12h ago). Every gone digest belongs to a quarterly one. So this is not a broken registry — it is untagged manifests being garbage-collected as tags move on.
And the mechanism is worse than it first looks: `secret-rotator` was rebuilt and republished **today** from unchanged source, because the build step also triggers on `.woodpecker/build-ci-images.yml`, which I edited while adding three unrelated images. So digest pins decay on a cadence driven by edits to a shared file that has nothing to do with the pinned image. Copying the sibling convention would have shipped a CronJob broken from the day it was written — it just would not have surfaced until 2026-10-01.
Full detail and the remediation for the other 11 in #458.
### 🟡 Dead link to the proposal doc
Correct, and the fix is merge order rather than an edit: **#453 should land before or with this.** Both references say so explicitly so the link is never silently wrong, but the cleanest sequence is #453 then #455.
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.
Phase 1 of #453. One of eight broker users; the other seven are untouched and keep their bootstrap Jobs.
Why
healthfirstIt's the broker's own liveness/readiness probe credential, consumed by exec probes inside the mosquitto container via the same Secret that seeds the passwd file. Nothing outside the pod holds it — so rotating it needs no coordination with any client. Smallest possible blast radius for proving
rotator → OpenBao → ESO → Reloaderend to end, before touching credentials held by Shelly firmware, the edge host, or the HA config entry.What it adds
Following
rotation/paperless.yamlexactly:rotator-mosquitto-health.hcl— per-secret policy,read+patchon that one path. Deliberately not a wildcard overmosquitto-*: users are being enrolled one at a time, and a wildcard would silently grant this rotator write access to the other seven before they're ready.rotation/mosquitto-health.yaml— SA + quarterly CronJob,secret-rotatorrandom mode. Scheduled a month off paperless so unrelated rotations don't share a window.mosquitto-health-credentials-externalsecret.yaml— projects back to the same Secret name and key, somosquitto-deployment.yaml'ssecretKeyRefand the probes reading$HEALTH_PASSWORDare unchanged.refreshInterval: 10mrather than evcc's1h, so a rotation lands promptly instead of leaving the old password valid for up to an hour./mosquitto/auth/passwdfrom the Secrets on pod start, so a rotated Secret would sit unused until some unrelated restart. Listed per-Secret rather thanauto: "true", so enrolling each further user stays a visible edit.Removes only the health bootstrap Job (508 → 440 lines). Deleting rather than editing also sidesteps the
spec.templateimmutability that made #449 unmergeable.Verified before committing
randommode fits unchanged. Env contract isBAO_ROLE/BAO_PATH/BAO_FIELDS/RANDOM_BYTES, and it callsassert_fields_existfirst — so it rotates an existing field and never bootstraps one. Seeding stays a documented one-time out-of-band step, as with every other rotator. That answers the first open question in #453.+,/,=); the old Jobs emitted alphanumeric viatr -dc 'A-Za-z0-9'. The probes pass it as-P "$HEALTH_PASSWORD"inside the container, never through a URL or config file. This must be re-checked per user before enrollingsml2mqtt/open3e(edge-host templates),shellies(device firmware) orhomeassistant. Noted in the rotator's header.passwordfield and its value still matches the live Secret (both23c10b3b), so ESO adoption changes nothing.eso-read.hclalready grantshomelab/data/*.mosquitto-health-secret-rotatorin nsopenbao, ttl 15m, verified readable.kustomize build+ server-side dry run clean on both the app and rotation kustomizations; six Jobs remain inmosquitto-bootstrap.yaml.Deliberately not done here
The first actual rotation. It's safe (in-cluster only, self-healing) but restarts the broker, so it belongs as a post-merge acceptance test rather than bundled into a merge:
Expect the OpenBao value to change, ESO to sync within 10m, Reloader to roll the broker, and probes to pass with the new password. Note the roll uses
maxUnavailable: 0/maxSurge: 1, so briefly two brokers with different passwd files sit behind the LB and a probe against the outgoing pod can fail auth — self-healing, and expected rather than alarming.🤖 Generated with Claude Code
🤖 Automated review — model
CLI default(complexity: complex), commit91133c94b044cc2d2827b9a3db901da15f2927e5.Summary
Removes the
healthbroker-user bootstrap Job and replaces it with an OpenBao-backed, Class-A-rotated credential (ExternalSecret + per-secret OpenBao policy + quarterly rotation CronJob + a Reloader annotation on the mosquitto Deployment), following the existingpaperlessrotation pattern. I cross-checked the new manifests againstrotation/paperless.yaml,rotator-paperless.hcl,eso-read.hcl, theopenbaoClusterSecretStore,mosquitto-deployment.yaml, and thesecret-rotatorRust source (randommode,assert_fields_exist, min-16-byte guard). Everything is internally consistent — paths, field names, role/SA names, the cron offset from paperless, and the six remaining bootstrap Jobs all line up with what the PR description claims. No functional bugs found.No blocking issues.
🟡 Suggestions
mosquitto-health-credentials-externalsecret.yaml:125-126,rotation/mosquitto-health.yaml:224-225, andpolicies/rotator-mosquitto-health.hcl:173-174all point tokubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.mdas the design doc for this rotation effort. That file is not present anywhere in the repo (checked working tree andgit log --all). Anyone following the reference — including whoever enrolls broker user #2 — will hit a 404. Either add the doc in this PR (or a companion one) or point the comments at wherever the actual plan lives (e.g. issue #453, linked in the PR body).🟢 Nits
mosquitto-deployment.yaml:192,202,245— pre-existing comments describe theHEALTH_PASSWORDandZIGBEE2MQTT_PASSWORDenv vars/probes as coming from "mosquitto-health-credentials-bootstrap" (a Job). This PR deletes that Job, so the comments now describe infrastructure that no longer exists. Not touched by this diff, but worth a follow-up cleanup since this PR is the one that made them stale.Tests
No automated tests apply to this class of change (matches the existing convention —
rotation/paperless.yamland its policy have none either). The PR body documents manual verification instead:kustomize build+ server-side dry-run on both kustomizations, and out-of-band confirmation that the OpenBao path already holds a matchingpasswordvalue. I couldn't runkustomize/kubectlmyself in this sandbox, but manual review of bothkustomization.yamlchanges (home-assistant app, openbao rotation) confirms the added resource entries point at files that exist and are well-formed, and the bootstrap file still contains exactly sixJobblocks post-deletion, matching the PR's claim. The one thing that is explicitly not tested here — the actual rotation round-trip (rotator → OpenBao → ESO → Reloader → broker) — is called out by the author as a deliberate post-merge acceptance step, which is a reasonable call given the brief probe-auth blip it can cause.Recommendation
Looks reasonable — no blocking issues; comments above are optional.
Both addressed in
2d24d0b6.🟡 Dangling doc reference
Correct, and it is a sequencing mistake on my part: I wrote the implementation referencing a design doc that has not landed.
PROPOSAL-mosquitto-credential-rotation.mdlives in #453, still open, so all three references pointed at nothing on master. Whoever enrols broker user #2 would have followed it into a 404.The references now state explicitly that the file arrives with #453 and does not resolve until it merges. Either merge order works — this PR no longer implies the doc is already there.
🟢 Stale bootstrap comments
Fixed here rather than deferred, since this PR is what made them stale. Both
HEALTH_PASSWORDcomments now point at the ExternalSecret instead of the deleted Job.One correction to the review: of the three lines flagged, only two were stale. The third (
mosquitto-zigbee2mqtt-credentials-bootstrap) is still accurate — that Job still exists, because onlyhealthis enrolled in this phase. It becomes stale when zigbee2mqtt is enrolled, and belongs to that change. Left alone deliberately rather than pre-emptively rewritten to describe something that is not true yet.Unchanged
Still
kustomize build+ server-side dry-run clean on both kustomizations, six Jobs remaining in mosquitto-bootstrap.yaml, and the OpenBao value still matching the live Secret so merging remains a no-op for the broker.🤖 Automated review — model
CLI default(complexity: complex), commit2d24d0b6d1d033b9887d37f1765cb9d7f082f49d.Summary
Migrates the mosquitto
healthbroker credential from a one-shot bootstrap Job to OpenBao-backed Class-A rotation (per-secret policy, quarterly CronJob, ExternalSecret, Reloader annotation), following the existingrotation/paperless.yamltemplate closely. I compared every new/changed file against the established patterns in this repo (paperless, evcc, home-assistant rotators) and everything lines up — env contract, security context, cron offsetting, HCL policy structure, and the Reloader annotation syntax all match. No blocking issues found.🟡 Suggestions
kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl: the other recently-added rotator policies that usebao kv patch(rotator-paperless.hcl:22,rotator-claude-oauth.hcl,rotator-review-bot.hcl) all carry the lineDo NOT set token_no_default_policy=true — the kv-v2 preflight needs the default policy. This new file omits it. Since these are applied manually and out-of-band (not reconciled by Flux), the warning exists specifically to stop someone from copy-pasting a hardenedbao write auth/kubernetes/role/...command that breaks thebao kv patchpreflight the rotator depends on. Worth adding for consistency before someone applies this role by hand.Tests
No automated tests apply to this class of change (Kubernetes manifests + OpenBao HCL policy, applied via Flux/manual bao commands). The PR body documents the equivalent verification:
kustomize build+ server-side dry-run on both kustomizations, and out-of-band confirmation that the OpenBao policy/role/seed value are correct and readable. That matches how other rotators in this repo were validated (e.g. paperless). The PR explicitly and reasonably defers the actual first rotation (which restarts the broker) to a documented post-merge on-demand Job rather than bundling it into the merge — a sound way to keep this change reversible and low-risk.I also checked for the exact issue this PR's own last commit says it was fixing (a dangling reference to
PROPOSAL-mosquitto-credential-rotation.md, which lives in unmerged PR #453, plus stale comments pointing at the now-deleted bootstrap Job): all three references to the proposal doc are now correctly caveated as "PR #453 — not yet merged," and a repo-wide grep for the deleted Job's name (mosquitto-health-credentials-bootstrap) turns up nothing — the fixup is complete and consistent.Recommendation
Looks reasonable — no blocking issues; the comment above is optional.
Added in the latest push. Verified both halves of the claim rather than just copying the line:
rotator-paperless,rotator-authentik,rotator-cloudflare,rotator-external-dns,rotator-gitea-flux), so it is a convention and mine was the outlier.token_no_default_policy: false— so this documents an existing good state rather than fixing a broken one.The reason it is worth having is exactly the one you give: these are applied by hand, out of band. Without the warning, this file is the one that looks safe to harden, and the failure mode is a rotation that stops working silently rather than an obvious error. I extended the wording slightly to say why (
the rotator writes via patch, so hardening the role that way would break rotation rather than tighten it) so the next person does not have to chaserotator.hclto find out.Thanks also for independently confirming the previous fixup was complete — the repo-wide grep for the deleted Job name turning up nothing is a better check than the one I ran.
🤖 Automated review — model
CLI default(complexity: complex), commit0817aaa24b72749c1aebacc85362c9e18a35dd46.Summary
Migrates the mosquitto
healthbroker credential (probe-only, used only by liveness/readiness exec probes inside the mosquitto container) from a one-shot bootstrap Job onto the repo's existing Class-A OpenBao rotation pattern (rotator CronJob + per-secret policy + ExternalSecret + Reloader annotation). It followsrotation/paperless.yamlclosely and I checked it against that template, the ESO/OpenBao path conventions,mosquitto-deployment.yaml, and the remaining bootstrap Jobs. Everything lines up; I found no blocking issues.🟡 Suggestions
kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl:214— references "see rotator.hcl", but no file by that name exists inkubernetes/infrastructure/openbao/policies/(onlyrotator-<app>.hclper-app files andeso-read.hcl). This is copied verbatim fromrotator-paperless.hcl:22, so it's a pre-existing dangling reference, not something this PR introduces — but since this PR is establishing the copy-this-file template for the other seven users, it might be worth fixing the source (rotator-paperless.hcl) so the reference doesn't keep propagating.secret-rotator@sha256:159e5888...) is reused byte-for-byte fromrotation/paperless.yaml, while several other rotation files in the same directory (home-assistant.yaml,claude-oauth.yaml,review-bot.yaml,woodpecker.yaml) pin different, presumably newer, digests. I couldn't confirm the chronology/changelog between these digests from the diff alone (git-log access was unavailable in this review environment), so I can't say whether the paperless-era image is missing a fix present in the newer ones — worth a quick check thatrandommode behaves identically across the digests in use before assuming parity.Tests
No automated tests apply to this class of change (Kustomize/OpenBao manifests) and the repo doesn't appear to have any for the sibling rotators either, so this is consistent with existing practice. The PR body explicitly and reasonably defers the one meaningful "test" — actually triggering
mosquitto-health-secret-rotate-nowand observing the OpenBao value change, ESO sync, Reloader roll, and probes passing — to a post-merge manual step, since it restarts the single-replica broker. That's a sound call for a manifests-only PR; I did not execute it.Static checks I performed in place of runtime tests:
policies/rotator-mosquitto-health.hcl's path (homelab/data/apps/home-assistant/mosquitto-health-credentials) matches the rotator'sBAO_PATH(homelab/apps/home-assistant/mosquitto-health-credentials) under thedata/KV-v2 convention used byrotator-paperless.hcl.dataFromkey (apps/home-assistant/mosquitto-health-credentials) matches the siblingmosquitto-evcc-credentials-externalsecret.yamlconvention, and thatsecretStoreRef(ClusterSecretStore/openbao) exists atkubernetes/infrastructure/post-crds/openbao-clustersecretstore.yaml.0 4 1 2,5,8,11 *) is quarterly, genuinely offset one month from paperless's (0 4 1 1,4,7,10 *), as claimed.mosquitto-health-credentials-bootstrapJob frommosquitto-bootstrap.yamlleaves the diff's---document separators intact and doesn't orphan the sharedmosquitto-bootstrapServiceAccount, which is still used by the six remaining Jobs.NetworkPolicyexists underkubernetes/infrastructure/openbao/that would block the new rotator Pod from reaching the in-namespace OpenBao service, matching the PR's "no NetworkPolicy needed" claim.mosquitto-deployment.yaml'ssecretKeyRefforHEALTH_PASSWORD(mosquitto-health-credentials/password) is unchanged and matches the new ExternalSecret'starget.nameand key, so the probes and passwd-file build keep working untouched.Recommendation
Looks reasonable — no blocking issues; comments above are optional.
The review asked whether copying paperless's rotator digest was safe. Checking it turned up something worse than a version-skew question: that digest does not exist in the registry at all. sha256:159e5888… ImagePullBackOff / NotFound Verified by pulling it, not by inspecting metadata. So this PR as written would have shipped a CronJob that fails on its first run in three months' time — silently, since a quarterly rotation failing looks like nothing happening. Now pinned to the digest :latest currently resolves to (sha256:dcf4267a…), verified to pull, to list `random` among its modes, and to carry the RANDOM_BYTES>=16 floor this file documents (RANDOM_BYTES=8 exits with "refusing to generate a weak secret" rather than generating one). BROADER PROBLEM, not introduced here and not fixed here: 11 of the 14 existing rotators are pinned to digests that are equally gone. Tested each: gone 159e5888 authentik, crowdsec-bouncer-key, paperless, shelly-manager, woodpecker, home-assistant gone 978fe53c cloudflare, external-dns gone 49bfddd8 netbird-ingress gone 97c22bcf pve-exporter present b41cc364 flux-git present 828bf486 claude-oauth, review-bot The two surviving digests belong to the rotators that run often; the gone ones are all quarterly or infrequent. Untagged manifests appear to be garbage-collected once :latest and the dated tag move past them, so a digest pinned today stops resolving after a couple of rebuilds. Every affected rotator will ImagePullBackOff on its next scheduled run unless the image happens to still be cached on whichever node it lands on. That means secret rotation across most of this cluster is probably already broken and would fail quietly. Raised separately rather than fixed here — it needs a decision (repin all, stop GC'ing, or tag-pin rather than digest-pin), not a drive-by edit inside a mosquitto PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>🤖 Automated review — model
CLI default(complexity: complex), commit4df89506fba15ddff24653b41b576676984bf7fc.Summary
This enrolls the mosquitto
healthbroker credential in the OpenBao-backed "Class-A" rotation pattern already used by paperless/authentik/woodpecker/etc.: new per-secret OpenBao policy, a quarterly rotation CronJob, an ExternalSecret projecting back to the existing Secret name/key, a Reloader annotation on the Deployment, and removal of the now-redundant bootstrap Job. I checked every factual claim in the PR description against the actual manifests and thesecret-rotatorRust source, and all of them held up: therandommode really does callassert_fields_existbefore patching (never bootstraps a field),random_secretreally does use standard (padded) base64 with+///=, theRANDOM_BYTES<16floor exists and rejects weak secrets, the schedule is genuinely offset one month from paperless's,home-assistantis already an allowed namespace on theopenbaoClusterSecretStore, and the six remaining bootstrap Jobs plus the removedhealthJob diff match what's described. No blocking issues found.🟡 Suggestions
mosquitto-bootstrap.yamlalready has no Job forevccinmaster(itsmosquitto-evcc-credentials-externalsecret.yamlpredates this PR and isn't part of this diff). So only six of the eight users still use bootstrap Jobs after this merges, not seven —evccwas already moved off that pattern (via ExternalSecret) before this PR, just without a rotation CronJob. Doesn't affect the shipped manifests, but worth correcting in the PR body/commit message so#453's phase count doesn't drift from reality.mosquitto-health-credentials-externalsecret.yaml:145,rotator-mosquitto-health.hcl:198, andmosquitto-health.yaml:253all point atkubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md, which doesn't exist in this checkout (confirmed viafind— it's added by #453, not yet merged). This is disclosed in-comment, so not a surprise, but if #455 merges to master before #453 lands, those references stay dead for whatever window separates the two merges. Worth merging #453 first, or squash-ordering them, if that isn't already the plan.🟢 Nits
rotator-mosquitto-health.hcl:214("Do NOT settoken_no_default_policy=true... seerotator.hcl") points at a file that doesn't exist in this repo (kubernetes/infrastructure/openbao/policies/rotator.hcl) — but this is copied verbatim fromrotator-paperless.hcl, which has the identical dangling reference, so it's a pre-existing issue in the template, not something this PR introduces.Tests
No automated tests exist for this class of change (OpenBao policies/CronJobs aren't Kubernetes-native-tested elsewhere in the repo either), consistent with every prior rotator PR. The PR body documents manual verification performed before commit (kustomize build + server-side dry run, live check that the new image digest resolves and
randomis a supported mode, live check that the OpenBao value already matches the Secret). The actual first rotation is deliberately deferred to a post-merge on-demandkubectl create jobrun rather than exercised here — reasonable given it restarts the broker, and the PR is explicit that this is intentionally left undone.Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 Automated review — model
CLI default(complexity: complex), commit9e75ab6b4d4fef96aece7f714618199262f810ac.Summary
Phase 1 of the mosquitto credential-rotation migration (#453): moves the broker's
healthuser off its one-shot bootstrap Job and onto OpenBao-backed Class-A rotation (ExternalSecret + per-secret rotator policy + CronJob), with a Reloader annotation so rotation actually takes effect. I checked every claim in the PR body against the checkout — the Secret name/key, ExternalSecret path convention, OpenBao policy path, and the reload/consumption wiring inmosquitto-deployment.yamlare all internally consistent and match the siblingmosquitto-evcc-credentials/rotator-paperlesspatterns exactly. No blocking issues found.🟡 Suggestions
kubernetes/infrastructure/openbao/rotation/mosquitto-health.yaml:352— the image is pinned by dated tag (secret-rotator:2026-07-30) rather than digest, explicitly diverging from every sibling rotator (paperless.yaml:66,woodpecker.yaml:66, etc., all@sha256:...). The stated reason (those digests were garbage-collected and would ImagePullBackOff, tracked as #458) is plausible and matches this repo's evidence — no non-:latest/digest-pinned rotator exists elsewhere — but I have no way to independently confirm from the checkout that the referenced digests are actually gone from the registry (no network access here). Worth having one other set of eyes confirm before merge, since it sets a precedent other rotators may want to follow once #458 is addressed.kubernetes/infrastructure/openbao/policies/rotator-mosquitto-health.hcl:198andkubernetes/infrastructure/openbao/rotation/mosquitto-health.yaml:253referencekubernetes/apps/home-assistant/PROPOSAL-mosquitto-credential-rotation.md, which does not exist in this checkout (confirmed via glob) since it ships in the not-yet-merged #453. This is called out explicitly in both places, so it's not misleading, just a currently-dead link — fine as long as #453 lands before/with this.Tests
No automated tests apply here (this is GitOps YAML + an out-of-band OpenBao policy/role, consistent with every other rotator in the repo — none have tests). The PR body documents
kustomize build+ server-side dry-run having been run on both kustomizations, and defers the actual first rotation to a documented post-merge manual step (kubectl create job --from=cronjob/...) rather than bundling a broker restart into the merge — reasonable given the blast radius is a live broker roll.Recommendation
Looks reasonable — no blocking issues; comments above are optional.
Both fair. Neither needs a code change, but the first deserves evidence rather than my say-so.
🟡 Dated tag diverging from every sibling
You are right to flag it and right that you could not confirm it from the checkout. Two things settle it:
1. It is now an owner directive, not my preference. Stated during this work: our own images do not need digest pinning, and
:latestis never to be used. So dated tags are the intended convention for registry-local images, matchingci-ansible:2026-04-29already in the repo and the three images added today (alpine-jq,cni-plugins,alpine-openssl, all:2026-07-30). This PR is not setting a precedent unilaterally — it is the first rotator to follow one that already exists elsewhere.2. The digests really are gone. Verified by pulling each in-cluster, since the registry API returns 401 for a PAT and metadata checks would have been misleading:
The control is the two that DO pull: both belong to rotators that run frequently (
claude-oauth-secret-syncis daily and ran 12h ago). Every gone digest belongs to a quarterly one. So this is not a broken registry — it is untagged manifests being garbage-collected as tags move on.And the mechanism is worse than it first looks:
secret-rotatorwas rebuilt and republished today from unchanged source, because the build step also triggers on.woodpecker/build-ci-images.yml, which I edited while adding three unrelated images. So digest pins decay on a cadence driven by edits to a shared file that has nothing to do with the pinned image. Copying the sibling convention would have shipped a CronJob broken from the day it was written — it just would not have surfaced until 2026-10-01.Full detail and the remediation for the other 11 in #458.
🟡 Dead link to the proposal doc
Correct, and the fix is merge order rather than an edit: #453 should land before or with this. Both references say so explicitly so the link is never silently wrong, but the cleanest sequence is #453 then #455.