feat(secret-rotator): pin image, reconcile path, unsuspend the two rotators #373

Merged
k.renner merged 2 commits from claude-bot/rotator-pin-unsuspend into master 2026-07-23 13:54:11 +00:00
Member

Activates the review-bot and claude-oauth rotators added in #363.

  • Pin both CronJobs to secret-rotator@sha256:828bf486… — the build that carries woodpecker-secret mode (the old pinned digest on the sibling rotators predates it and would exit "unknown mode").
  • Unsuspend both (suspend: false). Safe now because the image has the mode, the OpenBao policies/roles + bootstraps are applied, and the org secrets already exist — so a run maintains rather than bootstraps.
  • Reconcile the claude-oauth BAO_PATH/BAO_FIELD to homelab/infrastructure/claude / setup-token (where the OAuth value is actually seeded), with the matching rotator-claude-oauth.hcl path change (already applied out-of-band).

Cadence once merged: review-bot mints+syncs review_bot_token quarterly; claude-oauth syncs claude_code_oauth_token daily (04:20). Both write the org-2 secrets the fleet inherits (homelab, org 1, keeps its own repo-level copies).

kustomize builds clean; no :latest remains; both suspend:false.

Activates the review-bot and claude-oauth rotators added in #363. - **Pin** both CronJobs to `secret-rotator@sha256:828bf486…` — the build that carries `woodpecker-secret` mode (the old pinned digest on the sibling rotators predates it and would exit "unknown mode"). - **Unsuspend** both (`suspend: false`). Safe now because the image has the mode, the OpenBao policies/roles + bootstraps are applied, and the org secrets already exist — so a run maintains rather than bootstraps. - **Reconcile** the claude-oauth `BAO_PATH`/`BAO_FIELD` to `homelab/infrastructure/claude` / `setup-token` (where the OAuth value is actually seeded), with the matching `rotator-claude-oauth.hcl` path change (already applied out-of-band). Cadence once merged: review-bot mints+syncs `review_bot_token` quarterly; claude-oauth syncs `claude_code_oauth_token` daily (04:20). Both write the **org-2** secrets the fleet inherits (homelab, org 1, keeps its own repo-level copies). kustomize builds clean; no `:latest` remains; both suspend:false.
claude-bot added 1 commit 2026-07-23 13:00:52 +00:00
feat(secret-rotator): activate review-bot + claude-oauth rotators
ci/woodpecker/pr/pr-review Pipeline was successful
a9e1dfc85a
Pin both CronJobs to the built secret-rotator image that carries woodpecker-secret
mode (sha256:828bf486…), flip suspend:false, and reconcile the claude-oauth
BAO path to homelab/infrastructure/claude#setup-token (where the OAuth value is
actually seeded) — with the matching policy update (applied out-of-band).

Bootstraps + policies/roles are already applied and the org secrets are live, so
the rotators now maintain them on schedule: review-bot mints+syncs review_bot_token
quarterly; claude-oauth syncs claude_code_oauth_token daily.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
review-bot left a comment
Member

🤖 Automated review — model claude-sonnet-5 (complexity: normal), commit a9e1dfc85a4e88b544bec9b2c1c9ddb645a0c975.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR pins both review-bot and claude-oauth secret-rotator CronJobs to the secret-rotator@sha256:828bf486… digest, flips suspend: false on both, and reconciles the claude-oauth BAO_PATH/BAO_FIELD (+ matching OpenBao policy path) to homelab/infrastructure/claude#setup-token. The review-bot.yaml change is correct and consistent with the codebase's own secret-rotator binary (woodpecker-secret mode reads BAO_PATH#BAO_FIELD, default field token; sync_woodpecker_secret() in secret-rotator/src/main.rs:371-407). The claude-oauth.yaml change, however, drops the CronJob's spec: key while removing the old suspend-comment block, leaving suspend/schedule/timeZone/jobTemplate/etc. nested under metadata: instead of spec: — a structurally invalid CronJob that will fail to apply. This is a blocking issue.

🔴 Blocking

  • kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:31-49 — the diff hunk (diff.patch lines 26-40) deletes the old -spec: line along with the stale suspend-comment but never re-adds spec:. Checking the file as it stands after this PR:
    33  metadata:
    34    name: claude-oauth-secret-sync
    ...
    38    app.kubernetes.io/component: secret-rotator
    39    # Live: image pinned to the build that carries `woodpecker-secret` mode, the
    ...
    42    suspend: false
    43    schedule: "20 4 * * *" # daily, Europe/Berlin
    44    timeZone: Europe/Berlin
    45    concurrencyPolicy: Forbid
    46    successfulJobsHistoryLimit: 2
    47    failedJobsHistoryLimit: 5
    48    startingDeadlineSeconds: 3600
    49    jobTemplate:
    
    There is no top-level spec: key anywhere in this CronJob document (confirmed: grep -n '^spec:' claude-oauth.yaml returns nothing, vs. one match at review-bot.yaml:52). suspend, schedule, timeZone, concurrencyPolicy, and jobTemplate are all indented as siblings of labels under metadata:, i.e. they're being parsed as (invalid/unknown) ObjectMeta fields rather than CronJobSpec fields. metadata doesn't support these fields, and spec.schedule/spec.jobTemplate are required — so this manifest will be rejected by the API server (or by Flux's server-side apply) as invalid, not merely mis-scheduled. This defeats the entire purpose of the PR for claude-oauth: the rotator won't run at all, un-suspended or not.
    Note the PR body's "kustomize builds clean" claim doesn't catch this: kustomize build only concatenates/patches YAML, it does not validate against the Kubernetes OpenAPI schema for CronJob, so this passes kustomize but would fail kubectl apply/Flux reconciliation.
    Fix: re-add spec: before suspend: false at the same indent level as metadata:, and indent everything from suspend through the end of jobTemplate one level deeper (matching review-bot.yaml's structure).

🟡 Suggestions

  • kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:5 — the file's top-of-file doc comment still says the owner seeds the value into OpenBao at infrastructure/claude-code-oauth#token by hand, but this PR moves the actual path to infrastructure/claude#setup-token (both in this file's env vars and in policies/rotator-claude-oauth.hcl). If the owner later re-reads this comment to figure out where to re-seed a refreshed claude setup-token value, they'd write to the old (now-unauthorized-by-policy) path. Worth a one-line follow-up to keep the header comment in sync with the reconciled path — the rest of the PR already updates this same fact everywhere else it appears (rotator-claude-oauth.hcl:9,27, claude-oauth.yaml:39-41,84-89).

🟢 Nits

None beyond the above.

Tests

No automated tests apply here (these are static Kubernetes manifests + OpenBao policy files, no CI schema validation appears to run over kubernetes/infrastructure/openbao/rotation/*.yaml in this repo based on what's in the diff). Given that, a kubectl apply --dry-run=server (or kubeconform/kubectl-validate against the CronJob schema) against changed manifests before merge would have caught the missing spec: key immediately — worth considering as a lightweight CI gate for this directory going forward, though that's outside the scope of this PR.

Recommendation

Request changes — 1 blocking issue (invalid CronJob YAML structure in claude-oauth.yaml breaks the very activation this PR is meant to deliver).

🤖 **Automated review** — model `claude-sonnet-5` (complexity: normal), commit `a9e1dfc85a4e88b544bec9b2c1c9ddb645a0c975`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR pins both `review-bot` and `claude-oauth` secret-rotator CronJobs to the `secret-rotator@sha256:828bf486…` digest, flips `suspend: false` on both, and reconciles the claude-oauth `BAO_PATH`/`BAO_FIELD` (+ matching OpenBao policy path) to `homelab/infrastructure/claude#setup-token`. The `review-bot.yaml` change is correct and consistent with the codebase's own `secret-rotator` binary (`woodpecker-secret` mode reads `BAO_PATH#BAO_FIELD`, default field `token`; `sync_woodpecker_secret()` in `secret-rotator/src/main.rs:371-407`). The `claude-oauth.yaml` change, however, drops the CronJob's `spec:` key while removing the old suspend-comment block, leaving `suspend`/`schedule`/`timeZone`/`jobTemplate`/etc. nested under `metadata:` instead of `spec:` — a structurally invalid CronJob that will fail to apply. This is a blocking issue. ## 🔴 Blocking - `kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:31-49` — the diff hunk (`diff.patch` lines 26-40) deletes the old `-spec:` line along with the stale suspend-comment but never re-adds `spec:`. Checking the file as it stands after this PR: ``` 33 metadata: 34 name: claude-oauth-secret-sync ... 38 app.kubernetes.io/component: secret-rotator 39 # Live: image pinned to the build that carries `woodpecker-secret` mode, the ... 42 suspend: false 43 schedule: "20 4 * * *" # daily, Europe/Berlin 44 timeZone: Europe/Berlin 45 concurrencyPolicy: Forbid 46 successfulJobsHistoryLimit: 2 47 failedJobsHistoryLimit: 5 48 startingDeadlineSeconds: 3600 49 jobTemplate: ``` There is no top-level `spec:` key anywhere in this CronJob document (confirmed: `grep -n '^spec:' claude-oauth.yaml` returns nothing, vs. one match at `review-bot.yaml:52`). `suspend`, `schedule`, `timeZone`, `concurrencyPolicy`, and `jobTemplate` are all indented as siblings of `labels` under `metadata:`, i.e. they're being parsed as (invalid/unknown) `ObjectMeta` fields rather than `CronJobSpec` fields. `metadata` doesn't support these fields, and `spec.schedule`/`spec.jobTemplate` are required — so this manifest will be rejected by the API server (or by Flux's server-side apply) as invalid, not merely mis-scheduled. This defeats the entire purpose of the PR for claude-oauth: the rotator won't run at all, un-suspended or not. Note the PR body's "kustomize builds clean" claim doesn't catch this: `kustomize build` only concatenates/patches YAML, it does not validate against the Kubernetes OpenAPI schema for CronJob, so this passes kustomize but would fail `kubectl apply`/Flux reconciliation. **Fix:** re-add `spec:` before `suspend: false` at the same indent level as `metadata:`, and indent everything from `suspend` through the end of `jobTemplate` one level deeper (matching `review-bot.yaml`'s structure). ## 🟡 Suggestions - `kubernetes/infrastructure/openbao/rotation/claude-oauth.yaml:5` — the file's top-of-file doc comment still says `the owner seeds the value into OpenBao at infrastructure/claude-code-oauth#token by hand`, but this PR moves the actual path to `infrastructure/claude#setup-token` (both in this file's env vars and in `policies/rotator-claude-oauth.hcl`). If the owner later re-reads this comment to figure out where to re-seed a refreshed `claude setup-token` value, they'd write to the old (now-unauthorized-by-policy) path. Worth a one-line follow-up to keep the header comment in sync with the reconciled path — the rest of the PR already updates this same fact everywhere else it appears (`rotator-claude-oauth.hcl:9,27`, `claude-oauth.yaml:39-41,84-89`). ## 🟢 Nits None beyond the above. ## Tests No automated tests apply here (these are static Kubernetes manifests + OpenBao policy files, no CI schema validation appears to run over `kubernetes/infrastructure/openbao/rotation/*.yaml` in this repo based on what's in the diff). Given that, a `kubectl apply --dry-run=server` (or `kubeconform`/`kubectl-validate` against the CronJob schema) against changed manifests before merge would have caught the missing `spec:` key immediately — worth considering as a lightweight CI gate for this directory going forward, though that's outside the scope of this PR. ## Recommendation Request changes — 1 blocking issue (invalid CronJob YAML structure in `claude-oauth.yaml` breaks the very activation this PR is meant to deliver). <!-- pr-review:sha=a9e1dfc85a4e88b544bec9b2c1c9ddb645a0c975 -->
claude-bot added 1 commit 2026-07-23 13:26:01 +00:00
fix(secret-rotator): restore spec: key in claude-oauth CronJob
ci/woodpecker/pr/pr-review Pipeline was successful
0b4ccd66f8
The earlier edit that removed the stale suspend-comment block also dropped the
'spec:' line, leaving suspend/schedule/jobTemplate nested under metadata: — an
invalid CronJob that would be rejected by the API server (kustomize build does
not validate against the k8s schema, so it passed). Re-add spec: and sync the
top-of-file doc comment to the reconciled infrastructure/claude#setup-token
path. Caught by the review bot on this PR; verified with kubectl --dry-run=server.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
review-bot left a comment
Member

🤖 Automated review — model claude-opus-4-8 (complexity: complex), commit 0b4ccd66f8d85a956be5aa302288f38701591e68.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

Activates the two woodpecker-secret-mode rotators (review-bot, claude-oauth) by
pinning their images to the digest that carries that mode, flipping suspend: false,
and reconciling the claude-oauth source path/field to homelab/infrastructure/claude#setup-token.
The change is internally consistent and verifiable against the rotator source in-tree.
No blocking issues.

🟡 Suggestions

  • Un-suspend depends on out-of-band OpenBao state that the repo can't prove.
    rotation/claude-oauth.yaml:43 and rotation/review-bot.yaml:56 go live, but a
    successful run requires that (a) the reconciled policy rotator-claude-oauth.hcl
    (path homelab/data/infrastructure/claude, policies/rotator-claude-oauth.hcl:36)
    is actually re-applied to OpenBao, and (b) the OAuth value is seeded at the new
    path/field. The policy file itself notes it is "NOT applied by Flux… apply ONCE by
    hand" (policies/rotator-claude-oauth.hcl:12-18), and the PR body says this was
    done out-of-band. Failure mode is benign, not silent-wrong: read_field errors on
    a missing/empty source value (secret-rotator/src/bao.rs:71, used at
    main.rs:396), so a misconfigured path/field makes the 04:20 job fail rather
    than overwrite the working org secret with a blank — but it will fail nightly until
    the OpenBao side matches. Worth a one-line confirmation that both the new policy
    and the seeded value are in place before merge, since neither is checkable from the
    checkout.

  • claude-oauth still carries BOOTSTRAP_PATH/woodpecker-bot bootstrap (claude-oauth.yaml:83-84),
    which is correct — sync_woodpecker_secret reads the woodpecker-bot token from it
    (main.rs:374,393). No action; just confirming this wasn't left stale by the path
    reconcile.

Tests

No automated tests, consistent with the rest of this manifests directory. I verified
the change by reading the rotator source instead: woodpecker-secret mode exists
(main.rs:39), reads BAO_PATH/BAO_FIELD (main.rs:376-377, BAO_FIELD default
token), and bao::read_field maps the logical path to the kv-v2 API path
{mount}/data/{sub} (bao.rs:72) — so homelab/infrastructure/claude resolves to
homelab/data/infrastructure/claude, exactly the path the reconciled policy grants
read on. The digest 828bf48… is unique to these two rotators; the other rotators
carry older digests, matching the PR's claim that only this build has the mode. No
lingering claude-code-oauth references remain, and the only secret-rotator:latest
is the build target in .woodpecker/build-ci-images.yml:151, not a runtime image.

Recommendation

Looks reasonable — no blocking issues; comments above are optional (mainly: confirm
the out-of-band OpenBao policy re-apply + seeded value before merging, since the repo
can't verify them).

🤖 **Automated review** — model `claude-opus-4-8` (complexity: complex), commit `0b4ccd66f8d85a956be5aa302288f38701591e68`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary Activates the two `woodpecker-secret`-mode rotators (review-bot, claude-oauth) by pinning their images to the digest that carries that mode, flipping `suspend: false`, and reconciling the claude-oauth source path/field to `homelab/infrastructure/claude#setup-token`. The change is internally consistent and verifiable against the rotator source in-tree. No blocking issues. ## 🟡 Suggestions - **Un-suspend depends on out-of-band OpenBao state that the repo can't prove.** `rotation/claude-oauth.yaml:43` and `rotation/review-bot.yaml:56` go live, but a successful run requires that (a) the reconciled policy `rotator-claude-oauth.hcl` (path `homelab/data/infrastructure/claude`, `policies/rotator-claude-oauth.hcl:36`) is actually re-applied to OpenBao, and (b) the OAuth value is seeded at the new path/field. The policy file itself notes it is "NOT applied by Flux… apply ONCE by hand" (`policies/rotator-claude-oauth.hcl:12-18`), and the PR body says this was done out-of-band. Failure mode is benign, not silent-wrong: `read_field` errors on a missing/empty source value (`secret-rotator/src/bao.rs:71`, used at `main.rs:396`), so a misconfigured path/field makes the 04:20 job **fail** rather than overwrite the working org secret with a blank — but it will fail nightly until the OpenBao side matches. Worth a one-line confirmation that both the new policy and the seeded value are in place before merge, since neither is checkable from the checkout. - **claude-oauth still carries `BOOTSTRAP_PATH`/woodpecker-bot bootstrap** (`claude-oauth.yaml:83-84`), which is correct — `sync_woodpecker_secret` reads the woodpecker-bot token from it (`main.rs:374,393`). No action; just confirming this wasn't left stale by the path reconcile. ## Tests No automated tests, consistent with the rest of this manifests directory. I verified the change by reading the rotator source instead: `woodpecker-secret` mode exists (`main.rs:39`), reads `BAO_PATH`/`BAO_FIELD` (`main.rs:376-377`, `BAO_FIELD` default `token`), and `bao::read_field` maps the logical path to the kv-v2 API path `{mount}/data/{sub}` (`bao.rs:72`) — so `homelab/infrastructure/claude` resolves to `homelab/data/infrastructure/claude`, exactly the path the reconciled policy grants read on. The digest `828bf48…` is unique to these two rotators; the other rotators carry older digests, matching the PR's claim that only this build has the mode. No lingering `claude-code-oauth` references remain, and the only `secret-rotator:latest` is the build target in `.woodpecker/build-ci-images.yml:151`, not a runtime image. ## Recommendation Looks reasonable — no blocking issues; comments above are optional (mainly: confirm the out-of-band OpenBao policy re-apply + seeded value before merging, since the repo can't verify them). <!-- pr-review:sha=0b4ccd66f8d85a956be5aa302288f38701591e68 -->
k.renner merged commit 285b52eb55 into master 2026-07-23 13:54:11 +00:00
Sign in to join this conversation.