Files
homelab/kubernetes/infrastructure/openbao/rotation/review-bot.yaml
T
hermes-bot 6f1a7b6d96
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
fix(secret-rotator): never fail the HA run after the store; stage the image rollout
Folds the good parts of #1412 into this PR and addresses both review-bot
reviews (#1412 review 1789, #1414 review 1791):

- Everything after the OpenBao store is best-effort. Decode, reconnect,
  listing and revokes run in reap_superseded(); reap_summary() turns any
  error into "reap skipped" plus a warning, so rotate_ha_token returns Ok.
  A failed run would start a second pod (backoffLimit: 1) that keeps the
  token just stored and its successor and reaps the token consumers still
  hold until ESO syncs, i.e. the #1081 outage. Two unit tests cover it.
- Warn when the replaced token is not named "<client_name> [<6 hex>]":
  no later run reaps it, so it needs a manual revoke (from #1412).
- Staged image rollout (from #1412): only the three HA CronJobs carry the
  flux-system:secret-rotator marker; the other 17 rotators stay on
  2026-07-30 until a manual HA run validates the new binary, then a
  follow-up adds the marker to them. rotation/kustomization.yaml records
  the exception.
- Merged current main (a6f37e2b, 1691a0ac): the Reloader annotations for
  zone-configurator and speech-to-phrase are already there via #1409.
2026-10-21 07:59:52 +00:00

200 lines
8.7 KiB
YAML

---
# Rotate the `review-bot` Gitea PAT (review_bot_token) and sync it into
# Woodpecker's ORG-level secret store. One CronJob, two ordered steps:
#
# init `rotate-mint` — secret-rotator gitea-token: mint a fresh review-bot PAT
# via basic auth (review-bot's password bootstrap), verify it can PULL a
# private repo review-bot collaborates on (a real access check, not "any
# valid token" — the verify repo is private), store it at
# infrastructure/review-bot, reap old tokens keeping the two newest. Push is
# NOT verified: review-bot is a read collaborator that posts PR reviews via
# the API (needs write:repository scope) but never git-pushes.
#
# main `sync-woodpecker` — secret-rotator woodpecker-secret: read that token +
# the woodpecker-bot API token, upsert the konairius org secret
# `review_bot_token` with events cron,manual,pull_request. Org-level, so all
# four reviewed repos inherit it; no per-repo copies to keep aligned.
#
# The init step's mint is make-before-break (keeps the previous token live until
# ESO/Woodpecker have the new one), so if the sync step fails the pipeline keeps
# working on the prior org-secret value FOR THAT CYCLE. Caveat: the init step
# reaps old tokens (keeping the two newest) independent of whether sync then
# succeeds, so a sync failure that persists into the NEXT quarterly run would let
# that run reap the token Woodpecker is still referencing before the new one is
# synced. Quarterly cadence + backoffLimit:1 makes a two-cycle failure unlikely,
# but a failed sync should be fixed before the next run rather than left.
#
# gitea is reached in-cluster at gitea.gitea.svc:3000, woodpecker at
# woodpecker.woodpecker.svc:8000 (it moved out of `apps` in the namespace
# migration — see kubernetes/apps/woodpecker/MIGRATION-namespace.md). Needs
# the netpol halves: gitea-allow-review-bot-rotator + woodpecker-allow-rotators
# (woodpecker ns) and rotator-review-bot-egress (openbao-ns.yaml).
#
# OpenBao policy/role rotator-review-bot + the bootstraps (review-bot password,
# woodpecker-bot token, seed review-bot#token) are provisioned by hand — see
# policies/rotator-review-bot.hcl.
apiVersion: v1
kind: ServiceAccount
metadata:
name: review-bot-secret-rotator
namespace: openbao
labels:
app.kubernetes.io/name: review-bot
app.kubernetes.io/component: secret-rotator
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: review-bot-secret-rotate
namespace: openbao
labels:
app.kubernetes.io/name: review-bot
app.kubernetes.io/component: secret-rotator
spec:
# Live: image pinned to the build carrying `woodpecker-secret` mode, OpenBao
# policy/role + bootstraps (review-bot password, woodpecker-bot token) applied.
# Quarterly: mints review-bot's PAT and syncs it into the org secret.
suspend: false
schedule: "0 4 1 1,4,7,10 *" # quarterly, Europe/Berlin
timeZone: Europe/Berlin
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 5
startingDeadlineSeconds: 3600
jobTemplate:
spec:
backoffLimit: 1
activeDeadlineSeconds: 300
ttlSecondsAfterFinished: 259200 # 3 d
template:
metadata:
labels:
app.kubernetes.io/name: review-bot
app.kubernetes.io/component: secret-rotator
spec:
serviceAccountName: review-bot-secret-rotator
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 65534
runAsGroup: 65534
seccompProfile:
type: RuntimeDefault
initContainers:
- name: rotate-mint
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-07-30
args: ["gitea-token"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env:
- name: BAO_ADDR
value: http://openbao.openbao.svc.cluster.local:8200
- name: BAO_ROLE
value: rotator-review-bot
- name: BOOTSTRAP_PATH
value: homelab/infrastructure/secret-rotators-bootstrap/review-bot
- name: BAO_PATH
value: homelab/infrastructure/review-bot
- name: GITEA_URL
value: http://gitea.gitea.svc.cluster.local:3000
# Private repo review-bot collaborates on: the pull check is a
# real access verification (git-upload-pack on a PRIVATE repo
# needs a token with access), not the "any valid token" weakness
# a public repo would give (see main.rs:285-289). shelly-manager
# verified private 2026-07-23 (unauth GET -> 404; Woodpecker SCM
# PRIVATE=yes). IMPORTANT: if this repo is ever made public, the
# pull-only gate silently degrades to "is this any valid token" —
# then either set GITEA_REQUIRE_PUSH against a repo review-bot can
# push, or repoint this at another private repo it collaborates on.
- name: GITEA_VERIFY_REPO
value: konairius/shelly-manager
- name: GITEA_SCOPES
value: write:repository
# review-bot posts reviews via the API (write:repository scope)
# but never git-pushes, and is only a read collaborator, so push
# verification would fail. The private-repo pull check above is
# the load-bearing verification instead.
- name: GITEA_REQUIRE_PUSH
value: "false"
- name: GITEA_TOKEN_NAME_PREFIX
value: review-bot-rotated
resources:
requests:
cpu: 25m
memory: 16Mi
limits:
memory: 48Mi
# Read-only review-bot PAT for the Anthropic backend on the Gitea MCP server.
- name: rotate-mint-mcp
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-07-30
args: ["gitea-token"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env:
- name: BAO_ADDR
value: http://openbao.openbao.svc.cluster.local:8200
- name: BAO_ROLE
value: rotator-review-bot
- name: BOOTSTRAP_PATH
value: homelab/infrastructure/secret-rotators-bootstrap/review-bot
- name: BAO_PATH
value: homelab/infrastructure/review-bot
- name: BAO_TOKEN_FIELD
value: mcp_token
- name: GITEA_URL
value: http://gitea.gitea.svc.cluster.local:3000
- name: GITEA_VERIFY_REPO
value: konairius/shelly-manager
- name: GITEA_SCOPES
value: read:repository,read:issue,read:user
- name: GITEA_REQUIRE_PUSH
value: "false"
- name: GITEA_TOKEN_NAME_PREFIX
value: review-bot-mcp
resources:
requests:
cpu: 25m
memory: 16Mi
limits:
memory: 48Mi
containers:
- name: sync-woodpecker
image: gitea.renner.uno/gitea_admin/secret-rotator:2026-07-30
args: ["woodpecker-secret"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env:
- name: BAO_ADDR
value: http://openbao.openbao.svc.cluster.local:8200
- name: BAO_ROLE
value: rotator-review-bot
- name: BOOTSTRAP_PATH
value: homelab/infrastructure/secret-rotators-bootstrap/woodpecker-bot
- name: BAO_PATH
value: homelab/infrastructure/review-bot
- name: BAO_FIELD
value: token
- name: WOODPECKER_URL
value: http://woodpecker.woodpecker.svc.cluster.local:8000
- name: WOODPECKER_ORG
value: konairius
- name: WOODPECKER_SECRET_NAME
value: review_bot_token
- name: WOODPECKER_EVENTS
value: cron,manual,pull_request
resources:
requests:
cpu: 25m
memory: 16Mi
limits:
memory: 48Mi