Files
claude-botandClaude Opus 4.8 d78e703575
ci/woodpecker/push/build-ci-images Pipeline was successful
feat(secret-rotator): Rust rotator binary in a FROM-scratch image
Replaces the inline-Python approach before it grows a tenth copy. Today the
fleet carries ~130 lines of Python embedded in YAML strings across 9 manifests,
with auth/kubernetes/login duplicated in all 9 and the call()/merge-patch
helpers in 4 — none of it lintable, testable, or reviewable as code.

The Home Assistant rotator would have been the worst of them: a hand-rolled
WebSocket handshake with frame masking inside a YAML string. HA mints
long-lived access tokens only over its WebSocket API (verified against
2026.7.2 — auth/long_lived_access_token and auth/delete_refresh_token both
exist; the REST API has no equivalent).

Ships as a static musl binary in FROM scratch: 4.85 MB, one file, no libc, no
shell, no package manager. Nothing to patch and nothing for a compromised
rotator to exec. rustls with webpki-roots compiles the trust store into the
binary, so TLS works without a CA bundle on disk — in-cluster traffic is plain
HTTP, but that keeps the door open to take over the Cloudflare and Proxmox
rotators.

ha-token mode is make-before-break, and the ordering is load-bearing: list
existing token ids for the client_name BEFORE minting (so the new token can
never land in the revocation set), mint, write to OpenBao, and only then
revoke. If the OpenBao write fails, the old token is still valid and still
stored, so the run is a no-op rather than an outage.

A failed revoke is deliberately non-fatal but logged loudly: the new token is
already live, and a token meant to be retired that is still valid is a real
finding, not noise.

Verified: static-pie linked and stripped; image contains only the binary
(3.2 MB binary, 4.85 MB image); /bin/sh absent; exits non-zero on usage error;
cargo test passes. The pipeline path trigger now includes secret-rotator/**,
without which source changes would silently not rebuild the image.

No CronJob uses it yet — the Home Assistant rotator needs a network decision
first: the home-assistant namespace is default-deny-ingress, so openbao cannot
reach it, and the public name hairpins off the ingress LB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 12:14:39 +02:00

2 lines
8 B
Plaintext