[feature] OpenBao transit engine + broker + KV-sourced master key #27
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Execution tracker for the OpenBao integration. Design + plan live in Obsidian (source of truth):
edge-router/specs/2026-08-11-openbao-transit-broker-design.mdedge-router/plans/2026-08-11-openbao-transit-broker.mdBranch:
feat/openbao-transit-broker. Supersedes the transit portion of #20; unblocked by #18.What: opt-in OpenBao as the at-rest encryption engine (transit) and as a source for the master API key (KV), with all OpenBao credentials obtained from a local broker over a Unix socket — key material never enters the ER process. Local AES-GCM stays default; two independent toggles share one broker + one OpenBao.
Tasks (TDD, each independently testable):
broker.rs) — token over UDS, 90%-TTL cache, 403-refresh (mock-UDS-broker tests)CryptoEngineenum (Local | Transit); prefix-routed decrypt;.awaitrefactor of call sitesfile>inlineresolution flip + highest-security-wins-and-rejectskv_read+ v1/v2 field extraction)BAO_ADDR, real transit round-trip) + docsPre-flight: dev homelab runs a pre-per-service broker (one shared socket
/run/broker/broker.sock, broker itself registered) → no ER enrollment needed; Task 7 verifies against it. Confirm the actual transit key name + KV path/field at Task 7.Notes: migration is one-way (rollback needs a pre-migration DB snapshot);
ENCRYPTION_KEYresolution flips to file>inline (one non-additive behavior change). Verify the Alpine/musl Docker build after addinghyperlocal.Task 1 done —
e6ad026onfeat/openbao-transit-broker.broker.rs:TransitClient(token over UDS, ~90%-TTL cache, 403-refresh, HTTPS-only,Debug-redacted token) +is_transit_ciphertext. Offline tests via a mock UDS broker (cache-vs-refetch, https-only rejection, fail-closed-without-broker); real transit round-trip deferred to the gated live test (task 7). Deps added:hyperlocal,bytes. Full suite 202 green, clippy + fmt clean.Tracked debt:
mod brokeris#[allow(dead_code)]untilCryptoEngine(task 2) + startup wiring (task 6) construct aTransitClient. Remove the allow then.Tasks 2–7 remain. Task 2 is the broad async refactor of every
encrypt/decryptcall site (crypto.rs,health.rs,db.rs,middleware.rs,proxy.rs) — the security-sensitive core.All 7 tasks implemented on
feat/openbao-transit-broker(commitse6ad026,9da37d5,e4590ae,a5bc56a,d76a025):broker.rs) — token/UDS/cache/403-refresh, mock-broker testsCryptoEngine(Local|Transit) +.awaitrefactor of every call sitefile>inlineflip + highest-security-wins-and-rejects (pure, race-free tests)kv_read+ v1/v2 field extraction)Full suite 213 green (live test skips without
BAO_ADDR), clippy + fmt clean, zero dead-code allows. Alpine/musl Docker build (validateshyperlocal) in progress. Then: final review + merge tomain, and a live run against the dev broker to prove the real transit round-trip.Status: code-complete, blocked on live verification — NOT merged.
All offline gates pass on
feat/openbao-transit-broker: 213 unit tests, Alpine/musl Docker build (confirmshyperlocalcompiles), clippy + fmt clean, zero dead-code allows.But the feature has never run against real OpenBao — every passing test uses a mock UDS broker (canned token) or an injected
vault:v1:{pt}fake. Per our rule (verify external services against the real contract, don't assume documented behaviour), this must be proven before merge. Only a live run catches: the real transit response shape (.data.ciphertext/.data.plaintext+ base64), the actualvault:/bao:prefix (which migration idempotency + decrypt-routing depend on), and the broker's realGET /tokenJSON.Can't run it from the build host:
playground-broker-1's socket is not mounted on the host (/run/brokerabsent), anddevinfra-openbao-1is only on127.0.0.1:8200with an in-network cert the HTTPS-only client correctly rejects. The live test needs to run inside the dev environment where the broker socket is mounted and bao's cert is trusted:Prereqs to confirm there: the shared broker's policy grants a transit key ER can use (encrypt/decrypt), and (if testing KV) read on the master-key path. Merge to
mainis gated on this coming back green.Closing — merged to
mainas2a187f5on 2026-08-21. The checkboxes above were never ticked, so this looked unfinished; verified against the code onmaintoday:mainsrc/broker.rs(556 lines) — token over UDS, TTL cache, 403-refreshCryptoEngine(Local | Transit)src/crypto.rs(359 lines), prefix-routed decryptfile>inlineflipBAO_*resolution insrc/config.rsBAO_MASTER_KEY_PATH/BAO_MASTER_KEY_FIELDwired throughconfig.rs→main.rsdb.rs:956migrate_secrets_to_transit, injected re-encrypt seammain.rs, no dead-code allows remainbroker.rs:512openbao_live_round_trip, skips cleanly withoutBAO_ADDR; README documents the setupOne thing this close does not assert. The comment above gated merge on a live run against real OpenBao. The merge happened nine days later, so that gate presumably passed, but no result was recorded here — I'm inferring it from the merge, not from evidence. Separately, the live run covered the transit round-trip; KV master-key sourcing has never been exercised against a real instance, only against a mock broker and injected fakes.
That last gap is the only genuinely unproven part, so it gets its own issue rather than holding this one open.
Correction to my previous comment: the live verification was recorded, just not here. From my notes of 2026-08-24 —
broker::tests::openbao_live_round_trippassed against the real broker + OpenBao in devinfra, and the running devinfraedge-routerbooted clean on the merged code, loggingOpenBao broker reachable — credential path verified. ER was registered viadevinfra/scripts/provision-broker.sh edge-router, giving transit keyedge-routerand policyedge-router-transiton socket/run/broker/consumers/edge-router.sock.So the merge gate was genuinely satisfied — I was wrong to call it an inference.
That same note also explains precisely why KV was never exercised, which is better than "nobody tried": the provisioned policy grants encrypt/decrypt only, no KV read. It also hard-conflicts with the existing
MASTER_API_KEYenv under highest-security-source-wins. So #33 needs a policy extension and droppingMASTER_API_KEY, not just a test run — I've added that there.