feat(secrets): survive OpenBao outages and report real backend health #115
Reference in New Issue
Block a user
Delete Branch "feat/openbao-resilience"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Why
Two gaps, both verified against the live cluster.
OpenBao outages cascaded. mcpd re-read the backend on every secret use — server env resolution, LLM api keys, chat, git providers, code repos, webhooks — with no value cache, no request timeout, and a retry that only fired on HTTP 403. A sealed OpenBao answers 503, which was an immediate hard failure; an unreachable one hung its caller indefinitely. So a few seconds of unavailability became minutes of instance ERROR backoff (30s x5, then 5min forever).
mcpctl statuscould not tell you. TheSecrets:verdict came solely fromtokenMeta.lastRotationError, and the rotator writes that field only forauth: 'token'backends. The livebao-k8sbackend isauth: kubernetes, so it never wrote it — the tick was structurally incapable of going red, and reported OpenBao healthy while it was unreachable.What changed
CachingSecretBackendDriver— TTL cache with stale-while-error. On a transport failure it serves last-known-good rather than throwing, which is what stops the ERROR storm. A deleted secret evicts and rethrows: serving that stale would resurrect a revoked credential.plaintextis not wrapped.SecretNotFoundErrorvsSecretBackendUnavailableError. Typed rather than string-matched, because a mis-classification either resurrects deleted secrets or papers over revoked grants.GET /api/v1/secretbackends/:id/health—live(unauthenticated) vsready(real read).live && !readyis the re-initialised-OpenBao case; one boolean cannot express it. No RBAC mapping needed.mcpctl status— four states, rotation error demoted to a trailing clause. A failed probe renders? unknown, never green.BACKEND_TOKEN_DEADthrough pino — it was a bareconsole.error, bypassing the multistream feedingErrorLogBuffer, somcpctl errorsnever showed the one failure it exists for.Tests
39 new; 1048 mcpd + 730 cli green. The load-bearing guards were confirmed to fail against deliberately broken code before being kept — the 5 status assertions all fail against the old rotation-only logic, and the cache's never-serve-a-deleted-secret guard fails if the typed-error branch is removed.
SecretBackendRotatorLoophad zero coverage despite being the boot-time dead-token detector added after an upstream re-init broke every secret write for four days; it now has nine cases.Known state
src/mcplocal/tests/smoke/secret-resilience.smoke.test.tsfails until this is deployed — it asserts the probed rendering and the live cluster still returns the oldSecrets: bao-k8s* ✓. That is the intended signal, not a broken test.Cold cache during an outage still fails, by design (
e6cd735): booting a server with an empty credential is worse than failing loudly.