Final piece of the Flux-token work. Wires the dedicated flux gitea account's PAT into the rotation fleet — so the credential behind the recent GitOps outage can't silently die again. Uses the gitea-token mode from #348.
Flow
mint (basic auth, flux password bootstrap) → verify pull AND push → store in OpenBao → ESO merges into the flux-system secret → source-controller reads fresh. A token that fails verification is deleted and the run aborts without storing; the reap keeps the two newest so the previous token stays valid through the ESO propagation window.
Reachability + wiring
gitea via the in-cluster Service gitea.apps.svc:3000 (the public name hairpins off the ingress LB from a pod). Both netpol halves added.
flux-system added to the openbao ClusterSecretStore's allowed namespaces.
ExternalSecret creationPolicy: Merge — an ESO failure leaves Flux's last-good credential in place.
Completes the fleet: the Flux git credential now rotates quarterly with a pull+push safety gate. Policy/role + flux password bootstrap provisioned by hand (rotator-gitea-flux.hcl).
Final piece of the Flux-token work. Wires the dedicated `flux` gitea account's PAT into the rotation fleet — so the credential behind the recent GitOps outage can't silently die again. Uses the `gitea-token` mode from #348.
## Flow
mint (basic auth, flux password bootstrap) → **verify pull AND push** → store in OpenBao → ESO **merges** into the `flux-system` secret → source-controller reads fresh. A token that fails verification is deleted and the run aborts without storing; the reap **keeps the two newest** so the previous token stays valid through the ESO propagation window.
## Reachability + wiring
- gitea via the in-cluster Service `gitea.apps.svc:3000` (the public name hairpins off the ingress LB from a pod). Both netpol halves added.
- `flux-system` added to the openbao ClusterSecretStore's allowed namespaces.
- ExternalSecret `creationPolicy: Merge` — an ESO failure leaves Flux's last-good credential in place.
## Tested live end-to-end
```
job: minted flux-gitops-1b8d20, pull+push verified, stored (0 reaped)
OpenBao: e26e8bf1 -> 9666320e; gitea: new + previous KEPT
ESO synced -> flux-system secret
Flux pull: fetched; image-automation push: Ready=True
```
Completes the fleet: the Flux git credential now rotates quarterly with a pull+push safety gate. Policy/role + flux password bootstrap provisioned by hand (rotator-gitea-flux.hcl).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Wires the Flux git credential — the dedicated `flux` gitea account — into the
rotation fleet, so it can't silently die again (the cause of the recent GitOps
outage). Quarterly, via the secret-rotator binary's gitea-token mode.
Flow: mint a new PAT (basic auth, the flux password bootstrap) -> VERIFY it can
pull AND push (Flux image-automation writes back) -> store in OpenBao -> ESO
merges it into the flux-system secret -> source-controller reads it fresh. A
token that fails verification is deleted and the run aborts without storing, and
the reap keeps the two newest tokens so the previous one stays valid through the
ESO propagation window — Flux is never stranded.
Reachability: gitea via the in-cluster Service gitea.apps.svc:3000 — the public
gitea.renner.uno name hairpins off the ingress LB from a pod (same as
pve.renner.uno). Needs both netpol halves (gitea-allow-flux-rotator in apps +
rotator-gitea-flux-egress in openbao). Added flux-system to the openbao
ClusterSecretStore's allowed namespaces so the ExternalSecret can sync there.
The ExternalSecret uses creationPolicy: Merge, so an ESO failure leaves the
last-good credential in place rather than blanking Flux's secret.
Tested live end-to-end: job minted flux-gitops-1b8d20, verified pull+push,
stored (OpenBao e26e8bf1 -> 9666320e), kept the previous token (2 tokens, 0
reaped), ESO synced, Flux pull fetched, and image-automation pushed
(Ready=True). OpenBao policy/role + the gitea-flux password bootstrap are
provisioned by hand (see rotator-gitea-flux.hcl).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖Automated review — model claude-opus-4-8 (complexity: complex), commit 0d6fc81e9bf81f6307ccaa6208a8ab80b669f005.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
Adds the final piece of the Flux git-token rotation: a gitea-token-mode
rotator CronJob + ESO ExternalSecret that mints, pull/push-verifies, and
syncs the dedicated flux account's PAT into the flux-system git secret, plus
the two netpol halves and the ClusterSecretStore namespace allow. The wiring is
correct and consistent with the existing rotator fleet and the gitea-token
implementation in secret-rotator/src/main.rs. No blocking issues.
🔴 Blocking
None.
🟡 Suggestions
Pin GITEA_REQUIRE_PUSH: "true" explicitly in kubernetes/infrastructure/openbao/rotation/flux-git.yaml:200. verify_repo
here (k.renner/homelab) is a public repo (pr.json → repo.private: false), and the rotator's own comment
(secret-rotator/src/main.rs:283-287) warns that for a public repo the
pull check (git-upload-pack) returns 200 for any valid token regardless of
scope — so the push check is the only load-bearing verification. It currently
works because require_push defaults to true
(main.rs:255), but that safety property is invisible in this manifest and
would silently degrade to "is this any valid token" if the default ever
changed. Making it explicit documents the invariant at the call site. Optional.
creationPolicy: Merge won't recreate flux-system if it's deleted. ESO
Merge only patches an existing secret; if the flux-system secret is ever
removed (e.g. namespace rebuild) the rotated credential won't be re-seeded and
Flux git auth stays broken until the bootstrap runs again
(flux-git.yaml:135). This is the intended trade-off (a sync failure leaves
the last-good value in place, per the header comment) and matches how Flux
bootstrap owns creation, so no change needed — just confirming the failure
mode is understood.
🟢 Nits
Schedule 0 4 1 1,4,7,10 * coincides with ~10 other rotators at 04:00
(authentik, cloudflare, pve-exporter, etc.). This is the fleet norm and
they hit independent targets, so it's fine — the flux-git job is the only one
touching gitea's git endpoints. No action.
Tests
This is declarative Kubernetes/OpenBao config; the repo carries no manifest unit
tests for the sibling rotators, so none are expected here. The rotation logic it
drives lives in secret-rotator/src/main.rs::rotate_gitea_token and is exercised
there. The PR body documents a live end-to-end run (mint → pull+push verify →
OpenBao patch → ESO merge → Flux fetch + image-automation push Ready=True),
which is the right validation for an infra change of this shape. I verified the
static wiring by reading the code: bootstrap/target paths and read/patch
capabilities in policies/rotator-gitea-flux.hcl match the env vars and the assert_fields_exist+patch_field calls; the ESO key infrastructure/flux-git
resolves under the store's homelab v2 path to the policy's homelab/data/infrastructure/flux-git; the merged username/password keys
match what the flux-system GitRepository secretRef expects
(clusters/homelab/flux-system/gotk-sync.yaml); and both netpol halves' selectors
match the CronJob pod labels and the gitea endpoint selector used by the existing
metrics-scrape policy.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `claude-opus-4-8` (complexity: complex), commit `0d6fc81e9bf81f6307ccaa6208a8ab80b669f005`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
Adds the final piece of the Flux git-token rotation: a `gitea-token`-mode
rotator CronJob + ESO `ExternalSecret` that mints, pull/push-verifies, and
syncs the dedicated `flux` account's PAT into the `flux-system` git secret, plus
the two netpol halves and the ClusterSecretStore namespace allow. The wiring is
correct and consistent with the existing rotator fleet and the `gitea-token`
implementation in `secret-rotator/src/main.rs`. No blocking issues.
## 🔴 Blocking
None.
## 🟡 Suggestions
- **Pin `GITEA_REQUIRE_PUSH: "true"` explicitly** in
`kubernetes/infrastructure/openbao/rotation/flux-git.yaml:200`. `verify_repo`
here (`k.renner/homelab`) is a **public** repo (`pr.json` → `repo.private:
false`), and the rotator's own comment
(`secret-rotator/src/main.rs:283-287`) warns that for a public repo the
pull check (`git-upload-pack`) returns 200 for *any* valid token regardless of
scope — so the push check is the only load-bearing verification. It currently
works because `require_push` defaults to `true`
(`main.rs:255`), but that safety property is invisible in this manifest and
would silently degrade to "is this any valid token" if the default ever
changed. Making it explicit documents the invariant at the call site. Optional.
- **`creationPolicy: Merge` won't recreate `flux-system` if it's deleted.** ESO
Merge only patches an existing secret; if the `flux-system` secret is ever
removed (e.g. namespace rebuild) the rotated credential won't be re-seeded and
Flux git auth stays broken until the bootstrap runs again
(`flux-git.yaml:135`). This is the intended trade-off (a sync failure leaves
the last-good value in place, per the header comment) and matches how Flux
bootstrap owns creation, so no change needed — just confirming the failure
mode is understood.
## 🟢 Nits
- Schedule `0 4 1 1,4,7,10 *` coincides with ~10 other rotators at 04:00
(`authentik`, `cloudflare`, `pve-exporter`, etc.). This is the fleet norm and
they hit independent targets, so it's fine — the flux-git job is the only one
touching gitea's git endpoints. No action.
## Tests
This is declarative Kubernetes/OpenBao config; the repo carries no manifest unit
tests for the sibling rotators, so none are expected here. The rotation logic it
drives lives in `secret-rotator/src/main.rs::rotate_gitea_token` and is exercised
there. The PR body documents a live end-to-end run (mint → pull+push verify →
OpenBao patch → ESO merge → Flux fetch + image-automation push `Ready=True`),
which is the right validation for an infra change of this shape. I verified the
static wiring by reading the code: bootstrap/target paths and `read`/`patch`
capabilities in `policies/rotator-gitea-flux.hcl` match the env vars and the
`assert_fields_exist`+`patch_field` calls; the ESO key `infrastructure/flux-git`
resolves under the store's `homelab` v2 path to the policy's
`homelab/data/infrastructure/flux-git`; the merged `username`/`password` keys
match what the `flux-system` GitRepository `secretRef` expects
(`clusters/homelab/flux-system/gotk-sync.yaml`); and both netpol halves' selectors
match the CronJob pod labels and the gitea endpoint selector used by the existing
metrics-scrape policy.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=0d6fc81e9bf81f6307ccaa6208a8ab80b669f005 -->
Per review. k.renner/homelab is public, so the pull verification passes for any
valid token — push is the only load-bearing check. It already defaulted to true
in the binary, but pinning it in the manifest documents the safety invariant at
the call site so it can't silently degrade if the default ever changed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pinned GITEA_REQUIRE_PUSH: "true" in the manifest (commit pushed). You're right that push is the only load-bearing check here — the repo is public, so git-upload-pack passes for any valid token — and it should be visible at the call site, not just a binary default. Added with a comment explaining why.
creationPolicy: Merge won't recreate a deleted flux-system secret — confirmed, that's intended: Flux bootstrap owns creation, ESO owns the values, and Merge means a sync failure leaves the last-good credential rather than blanking Flux's git auth. On a namespace rebuild the bootstrap re-seeds it (documented in the cold-rebuild runbook path). No change.
Schedule at 04:00 with the other rotators — fleet norm, independent targets; flux-git is the only one touching gitea. No action.
Thanks for the static-wiring verification — good to have the paths/capabilities/netpol-selectors independently confirmed.
## Review addressed — no blockers
**Pinned `GITEA_REQUIRE_PUSH: "true"`** in the manifest (commit pushed). You're right that push is the only load-bearing check here — the repo is public, so `git-upload-pack` passes for any valid token — and it should be visible at the call site, not just a binary default. Added with a comment explaining why.
**`creationPolicy: Merge` won't recreate a deleted `flux-system` secret** — confirmed, that's intended: Flux bootstrap owns creation, ESO owns the values, and Merge means a sync failure leaves the last-good credential rather than blanking Flux's git auth. On a namespace rebuild the bootstrap re-seeds it (documented in the cold-rebuild runbook path). No change.
**Schedule at 04:00 with the other rotators** — fleet norm, independent targets; flux-git is the only one touching gitea. No action.
Thanks for the static-wiring verification — good to have the paths/capabilities/netpol-selectors independently confirmed.
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.
Final piece of the Flux-token work. Wires the dedicated
fluxgitea account's PAT into the rotation fleet — so the credential behind the recent GitOps outage can't silently die again. Uses thegitea-tokenmode from #348.Flow
mint (basic auth, flux password bootstrap) → verify pull AND push → store in OpenBao → ESO merges into the
flux-systemsecret → source-controller reads fresh. A token that fails verification is deleted and the run aborts without storing; the reap keeps the two newest so the previous token stays valid through the ESO propagation window.Reachability + wiring
gitea.apps.svc:3000(the public name hairpins off the ingress LB from a pod). Both netpol halves added.flux-systemadded to the openbao ClusterSecretStore's allowed namespaces.creationPolicy: Merge— an ESO failure leaves Flux's last-good credential in place.Tested live end-to-end
Completes the fleet: the Flux git credential now rotates quarterly with a pull+push safety gate. Policy/role + flux password bootstrap provisioned by hand (rotator-gitea-flux.hcl).
🤖 Generated with Claude Code
🤖 Automated review — model
claude-opus-4-8(complexity: complex), commit0d6fc81e9bf81f6307ccaa6208a8ab80b669f005.Summary
Adds the final piece of the Flux git-token rotation: a
gitea-token-moderotator CronJob + ESO
ExternalSecretthat mints, pull/push-verifies, andsyncs the dedicated
fluxaccount's PAT into theflux-systemgit secret, plusthe two netpol halves and the ClusterSecretStore namespace allow. The wiring is
correct and consistent with the existing rotator fleet and the
gitea-tokenimplementation in
secret-rotator/src/main.rs. No blocking issues.🔴 Blocking
None.
🟡 Suggestions
Pin
GITEA_REQUIRE_PUSH: "true"explicitly inkubernetes/infrastructure/openbao/rotation/flux-git.yaml:200.verify_repohere (
k.renner/homelab) is a public repo (pr.json→repo.private: false), and the rotator's own comment(
secret-rotator/src/main.rs:283-287) warns that for a public repo thepull check (
git-upload-pack) returns 200 for any valid token regardless ofscope — so the push check is the only load-bearing verification. It currently
works because
require_pushdefaults totrue(
main.rs:255), but that safety property is invisible in this manifest andwould silently degrade to "is this any valid token" if the default ever
changed. Making it explicit documents the invariant at the call site. Optional.
creationPolicy: Mergewon't recreateflux-systemif it's deleted. ESOMerge only patches an existing secret; if the
flux-systemsecret is everremoved (e.g. namespace rebuild) the rotated credential won't be re-seeded and
Flux git auth stays broken until the bootstrap runs again
(
flux-git.yaml:135). This is the intended trade-off (a sync failure leavesthe last-good value in place, per the header comment) and matches how Flux
bootstrap owns creation, so no change needed — just confirming the failure
mode is understood.
🟢 Nits
0 4 1 1,4,7,10 *coincides with ~10 other rotators at 04:00(
authentik,cloudflare,pve-exporter, etc.). This is the fleet norm andthey hit independent targets, so it's fine — the flux-git job is the only one
touching gitea's git endpoints. No action.
Tests
This is declarative Kubernetes/OpenBao config; the repo carries no manifest unit
tests for the sibling rotators, so none are expected here. The rotation logic it
drives lives in
secret-rotator/src/main.rs::rotate_gitea_tokenand is exercisedthere. The PR body documents a live end-to-end run (mint → pull+push verify →
OpenBao patch → ESO merge → Flux fetch + image-automation push
Ready=True),which is the right validation for an infra change of this shape. I verified the
static wiring by reading the code: bootstrap/target paths and
read/patchcapabilities in
policies/rotator-gitea-flux.hclmatch the env vars and theassert_fields_exist+patch_fieldcalls; the ESO keyinfrastructure/flux-gitresolves under the store's
homelabv2 path to the policy'shomelab/data/infrastructure/flux-git; the mergedusername/passwordkeysmatch what the
flux-systemGitRepositorysecretRefexpects(
clusters/homelab/flux-system/gotk-sync.yaml); and both netpol halves' selectorsmatch the CronJob pod labels and the gitea endpoint selector used by the existing
metrics-scrape policy.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
Review addressed — no blockers
Pinned
GITEA_REQUIRE_PUSH: "true"in the manifest (commit pushed). You're right that push is the only load-bearing check here — the repo is public, sogit-upload-packpasses for any valid token — and it should be visible at the call site, not just a binary default. Added with a comment explaining why.creationPolicy: Mergewon't recreate a deletedflux-systemsecret — confirmed, that's intended: Flux bootstrap owns creation, ESO owns the values, and Merge means a sync failure leaves the last-good credential rather than blanking Flux's git auth. On a namespace rebuild the bootstrap re-seeds it (documented in the cold-rebuild runbook path). No change.Schedule at 04:00 with the other rotators — fleet norm, independent targets; flux-git is the only one touching gitea. No action.
Thanks for the static-wiring verification — good to have the paths/capabilities/netpol-selectors independently confirmed.